diff options
| author | marios <marios8543@gmail.com> | 2022-04-03 23:50:26 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-04-03 23:50:26 +0300 |
| commit | 5e9c12bac838730d4e216b3779227a9a94447e40 (patch) | |
| tree | 61f50207f0d45f6fdab09c31d2b35778df8aff63 | |
| parent | fb6f55a44deef64a0efff9cc645368b946ea897d (diff) | |
| download | decky-loader-5e9c12bac838730d4e216b3779227a9a94447e40.tar.gz decky-loader-5e9c12bac838730d4e216b3779227a9a94447e40.zip | |
Python rewrite (#6)
* Initial commit. Untested
* various fixes
Core functionality confirmed working:
- Iframe injection into steam client
- Plugin fetching from the iframe
- Plugin opening
* Added function to fetch resources from steam
* Improved injector module, added server-js communication
- Injector module now has methods for better lower-level manipulation of the tab debug websocket.
- Our "front-end" can now communicate with the manager (2-way), completely bypassing the chromium sandbox. This works via a dirty debug console trick, whoever wants to know how it works can take a look at the code.
- Added utility methods file, along with an implementation of the aiohttp client that our "front-end" can access, via the system described above.
- Added js implementations of the communication system described above, which can be imported by plugins.
* Added steam_resource endpoint
* Added basic installer script
* retry logic bug fix
* fixed library injection, event propagation, websocket handling
- library is injected directly into the plugins as well as the plugin list
- resolveMethodCall is implemented in the plugin_list.js file, which in turns calls window.sendMessage on the iframe to propagate the event
- websocket method calls are processed in their own tasks now, so as not to block on long-running calls.
Co-authored-by: tza <tza@hidden>
Co-authored-by: WerWolv <werwolv98@gmail.com>
| -rw-r--r-- | .cargo/config | 5 | ||||
| -rw-r--r-- | .gitignore | 154 | ||||
| -rw-r--r-- | .idea/.gitignore | 8 | ||||
| -rw-r--r-- | .idea/SteamOS-Plugin-Manager.iml | 11 | ||||
| -rw-r--r-- | .idea/modules.xml | 8 | ||||
| -rw-r--r-- | .idea/vcs.xml | 6 | ||||
| -rw-r--r-- | Cargo.lock | 843 | ||||
| -rw-r--r-- | Cargo.toml | 13 | ||||
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | install.sh | 37 | ||||
| -rw-r--r-- | plugin_loader.service | 13 | ||||
| -rw-r--r-- | plugin_loader/injector.py | 85 | ||||
| -rw-r--r-- | plugin_loader/loader.py | 60 | ||||
| -rw-r--r-- | plugin_loader/main.py | 77 | ||||
| -rw-r--r-- | plugin_loader/static/library.js | 41 | ||||
| -rw-r--r-- | plugin_loader/static/plugin_page.js | 44 | ||||
| -rw-r--r-- | plugin_loader/templates/plugin_view.html | 31 | ||||
| -rw-r--r-- | plugin_loader/utilities.py | 18 | ||||
| -rw-r--r-- | plugin_manager.service | 9 | ||||
| -rw-r--r-- | plugin_template.py | 14 | ||||
| -rw-r--r-- | requirements.txt | 2 | ||||
| -rw-r--r-- | src/main.rs | 148 | ||||
| -rw-r--r-- | src/plugin_page.js | 82 | ||||
| -rw-r--r-- | template_plugin.js | 15 |
24 files changed, 583 insertions, 1153 deletions
diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 39f99b73..00000000 --- a/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[build] -target = "x86_64-unknown-linux-musl" - -[env] -PKG_CONFIG_ALLOW_CROSS = "1"
\ No newline at end of file @@ -1 +1,153 @@ -/target +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/
\ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/SteamOS-Plugin-Manager.iml b/.idea/SteamOS-Plugin-Manager.iml deleted file mode 100644 index c254557e..00000000 --- a/.idea/SteamOS-Plugin-Manager.iml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module type="CPP_MODULE" version="4"> - <component name="NewModuleRootManager"> - <content url="file://$MODULE_DIR$"> - <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> - <excludeFolder url="file://$MODULE_DIR$/target" /> - </content> - <orderEntry type="inheritedJdk" /> - <orderEntry type="sourceFolder" forTests="false" /> - </component> -</module>
\ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 8875d58a..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="ProjectModuleManager"> - <modules> - <module fileurl="file://$PROJECT_DIR$/.idea/SteamOS-Plugin-Manager.iml" filepath="$PROJECT_DIR$/.idea/SteamOS-Plugin-Manager.iml" /> - </modules> - </component> -</project>
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="VcsDirectoryMappings"> - <mapping directory="$PROJECT_DIR$" vcs="Git" /> - </component> -</project>
\ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 18cba49d..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,843 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", -] - -[[package]] -name = "h2" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "mio" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" - -[[package]] -name = "parking_lot" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "serde" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "steamos_plugin_manager" -version = "0.1.0" -dependencies = [ - "hyper", - "serde", - "serde_json", - "tokio", - "tungstenite", -] - -[[package]] -name = "syn" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" - -[[package]] -name = "windows_i686_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" - -[[package]] -name = "windows_i686_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b996bb66..00000000 --- a/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "steamos_plugin_manager" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tokio = { version = "1", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tungstenite = "0.17.2" -hyper = { version = "0.14.18", features = [ "full" ] }
\ No newline at end of file @@ -1,3 +1,4 @@ +# This is a Work-In-Progress (WIP) # SteamOS Plugin Manager  @@ -7,17 +8,20 @@ - Under System -> System Settings toggle `Enable Developer Mode` - Scroll the sidebar all the way down and click on `Developer` - Under Miscellaneous, enable `CEF Remote Debugging` -- Place the executable under `~/homebrew/services/plugin_manager`. Do not change the name of the file. +- Place the executable under `~/homebrew/services/plugin_loader`. Do not change the name of the file. - Place the plugin_manager.service file under `/etc/systemd/system` -- Open a Terminal and type `sudo systemctl --now enable plugin_manager` +- Open a Terminal and type `systemctl --now --user enable plugin_manager` ### Install Plugins -- Simply copy the plugin's .js file into `~/homebrew/services/plugin_manager/plugins` +- Simply copy the plugin's .py file into `~/homebrew/plugins` ## Features - Clean injecting and loading of one or more plugins - Persistent. It doesn't need to be reinstalled after every system update +- Allows 2-way communication between the plugins and the loader. +- Allows plugins to define python functions and run them from javascript. +- Allows plugins to make fetch calls, bypassing cors completely. ## Credit -The original idea for the concept is based on the work of [marios8543's steamdeck-ui-inject](https://github.com/marios8543/steamdeck-ui-inject) project. +The original idea for the concept is based on the work of [marios8543's steamdeck-ui-inject](https://github.com/marios8543/steamdeck-ui-inject) project.
\ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 00000000..f24cb83f --- /dev/null +++ b/install.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +if [ "$EUID" -ne 0 ]; then + echo "Please run this script as root" + exit +fi + +HOMEBREW_FOLDER=/home/deck/homebrew +LOADER_FOLDER=$(realpath $(dirname "$0")) + +# Create folder structure +rm -rf ${HOMEBREW_FOLDER}/services/plugin_loader +mkdir -p ${HOMEBREW_FOLDER}/services/plugin_loader +mkdir -p ${HOMEBREW_FOLDER}/plugins +chown -R deck ${HOMEBREW_FOLDER} + +# Install our files +cp -a ${LOADER_FOLDER}/plugin_loader/. /home/deck/homebrew/services/plugin_loader/ + +# Install pip if it's not installed yet +python -m pip &> /dev/null +if [ $? -ne 0 ]; then + curl https://bootstrap.pypa.io/get-pip.py --output /tmp/get-pip.py + python /tmp/get-pip.py +fi + +# Install dependencies +python -m pip install -r requirements.txt + +# Create a service +systemctl stop plugin_loader + +cp ./plugin_loader.service /etc/systemd/system/plugin_loader.service + +systemctl daemon-reload +systemctl enable plugin_loader +systemctl start plugin_loader
\ No newline at end of file diff --git a/plugin_loader.service b/plugin_loader.service new file mode 100644 index 00000000..9f532fa3 --- /dev/null +++ b/plugin_loader.service @@ -0,0 +1,13 @@ +[Unit] +Description=SteamDeck Plugin Manager + +[Service] +Type=simple + +ExecStart=/usr/bin/python3 /home/deck/homebrew/services/plugin_loader/main.py +WorkingDirectory/home/deck/homebrew/services/plugin_loader + +Environment=PLUGIN_PATH=/home/deck/homebrew/plugins + +[Install] +WantedBy=default.target
\ No newline at end of file diff --git a/plugin_loader/injector.py b/plugin_loader/injector.py new file mode 100644 index 00000000..771d5c51 --- /dev/null +++ b/plugin_loader/injector.py @@ -0,0 +1,85 @@ +#Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there. + +from aiohttp import ClientSession +from logging import info +from asyncio import sleep + +BASE_ADDRESS = "http://localhost:8080" + +class Tab: + def __init__(self, res) -> None: + self.title = res["title"] + self.id = res["id"] + self.ws_url = res["webSocketDebuggerUrl"] + + self.websocket = None + self.client = None + + async def open_websocket(self): + self.client = ClientSession() + self.websocket = await self.client.ws_connect(self.ws_url) + + async def listen_for_message(self): + async for message in self.websocket: + yield message + + async def _send_devtools_cmd(self, dc, receive=True): + if self.websocket: + await self.websocket.send_json(dc) + return (await self.websocket.receive_json()) if receive else None + raise RuntimeError("Websocket not opened") + + async def evaluate_js(self, js): + await self.open_websocket() + res = await self._send_devtools_cmd({ + "id": 1, + "method": "Runtime.evaluate", + "params": { + "expression": js, + "userGesture": True + } + }) + await self.client.close() + return res + + async def get_steam_resource(self, url): + await self.open_websocket() + res = await self._send_devtools_cmd({ + "id": 1, + "method": "Runtime.evaluate", + "params": { + "expression": f'(async function test() {{ return await (await fetch("{url}")).text() }})()', + "userGesture": True, + "awaitPromise": True + } + }) + await self.client.close() + return res["result"]["result"]["value"] + + def __repr__(self): + return self.title + +async def get_tabs(): + async with ClientSession() as web: + res = {} + + while True: + try: + res = await web.get("{}/json".format(BASE_ADDRESS)) + break + except: + print("Steam isn't available yet. Wait for a moment...") + await sleep(5) + + if res.status == 200: + res = await res.json() + return [Tab(i) for i in res] + else: + raise Exception("/json did not return 200. {}".format(await res.text())) + +async def inject_to_tab(tab_name, js): + tabs = await get_tabs() + tab = next((i for i in tabs if i.title == tab_name), None) + if not tab: + raise ValueError("Tab {} not found in running tabs".format(tab_name)) + info(await tab.evaluate_js(js)) diff --git a/plugin_loader/loader.py b/plugin_loader/loader.py new file mode 100644 index 00000000..0ed58b39 --- /dev/null +++ b/plugin_loader/loader.py @@ -0,0 +1,60 @@ +from aiohttp import web +from aiohttp_jinja2 import template + +from os import path, listdir +from importlib.util import spec_from_file_location, module_from_spec +from logging import getLogger + +import injector + +class Loader: + def __init__(self, server_instance, plugin_path) -> None: + self.logger = getLogger("Loader") + self.plugin_path = plugin_path + self.plugins = self.import_plugins() + + server_instance.add_routes([ + web.get("/plugins/iframe", self.plugin_iframe_route), + web.get("/plugins/reload", self.reload_plugins), + web.post("/plugins/method_call", self.handle_plugin_method_call), + web.get("/plugins/load/{name}", self.load_plugin), + web.get("/steam_resource/{path:.+}", self.get_steam_resource) + ]) + + def import_plugins(self): + files = [i for i in listdir(self.plugin_path) if i.endswith(".py")] + dc = {} + for file in files: + try: + spec = spec_from_file_location("_", path.join(self.plugin_path, file)) + module = module_from_spec(spec) + spec.loader.exec_module(module) + dc[module.Plugin.name] = module.Plugin + self.logger.info("Loaded {}".format(module.Plugin.name)) + except Exception as e: + self.logger.error("Could not load {}. {}".format(file, e)) + return dc + + async def reload_plugins(self, request=None): + self.logger.info("Re-importing all plugins.") + self.plugins = self.import_plugins() + + async def handle_plugin_method_call(self, plugin_name, method_name, **kwargs): + return await getattr(self.plugins[plugin_name], method_name)(**kwargs) + + async def get_steam_resource(self, request): + tab = (await injector.get_tabs())[0] + return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") + + async def load_plugin(self, request): + plugin = self.plugins[request.match_info["name"]] + ret = """ + <script src="/static/library.js"></script> + <script>const plugin_name = '{}' </script> + {} + """.format(plugin.name, plugin.main_view_html) + return web.Response(text=ret, content_type="text/html") + + @template('plugin_view.html') + async def plugin_iframe_route(self, request): + return {"plugins": self.plugins.values()} diff --git a/plugin_loader/main.py b/plugin_loader/main.py new file mode 100644 index 00000000..4ecb5158 --- /dev/null +++ b/plugin_loader/main.py @@ -0,0 +1,77 @@ +from aiohttp.web import Application, run_app, static +from aiohttp_jinja2 import setup as jinja_setup +from jinja2 import FileSystemLoader +from os import getenv, path +from asyncio import get_event_loop +from json import loads, dumps + +from loader import Loader +from injector import inject_to_tab, get_tabs +from utilities import util_methods + +CONFIG = { + "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"), + "server_host": getenv("SERVER_HOST", "127.0.0.1"), + "server_port": int(getenv("SERVER_PORT", "1337")) +} + +class PluginManager: + def __init__(self) -> None: + self.loop = get_event_loop() + self.web_app = Application() + self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"]) + + jinja_setup(self.web_app, loader=FileSystemLoader(path.join(path.dirname(__file__), 'templates'))) + self.web_app.on_startup.append(self.inject_javascript) + self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) + self.loop.create_task(self.method_call_listener()) + + async def resolve_method_call(self, tab, call_id, response): + await tab._send_devtools_cmd({ + "id": 1, + "method": "Runtime.evaluate", + "params": { + "expression": "resolveMethodCall({}, {})".format(call_id, dumps(response)), + "userGesture": True + } + }, receive=False) + + async def handle_method_call(self, method, tab): + res = {} + try: + if method["method"] == "plugin_method": + res["result"] = await self.plugin_loader.handle_plugin_method_call( + method["args"]["plugin_name"], + method["args"]["method_name"], + **method["args"]["args"] + ) + res["success"] = True + else: + r = await util_methods[method["method"]](**method["args"]) + res["result"] = r + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + finally: + await self.resolve_method_call(tab, method["id"], res) + + async def method_call_listener(self): + tab = next((i for i in await get_tabs() if i.title == "QuickAccess"), None) + await tab.open_websocket() + await tab._send_devtools_cmd({"id": 1, "method": "Runtime.discardConsoleEntries"}) + await tab._send_devtools_cmd({"id": 1, "method": "Runtime.enable"}) + async for message in tab.listen_for_message(): + data = message.json() + if not "id" in data and data["method"] == "Runtime.consoleAPICalled" and data["params"]["type"] == "debug": + method = loads(data["params"]["args"][0]["value"]) + self.loop.create_task(self.handle_method_call(method, tab)) + + async def inject_javascript(self, request=None): + await inject_to_tab("QuickAccess", open(path.join(path.dirname(__file__), "static/plugin_page.js"), "r").read()) + + def run(self): + return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop) + +if __name__ == "__main__": + PluginManager().run()
\ No newline at end of file diff --git a/plugin_loader/static/library.js b/plugin_loader/static/library.js new file mode 100644 index 00000000..1e35eee6 --- /dev/null +++ b/plugin_loader/static/library.js @@ -0,0 +1,41 @@ +class PluginEventTarget extends EventTarget { } +method_call_ev_target = new PluginEventTarget(); + +window.addEventListener("message", function(evt) { + console.log(evt); + let ev = new Event(evt.data.call_id); + ev.data = evt.data.result; + method_call_ev_target.dispatchEvent(ev); +}, false); + +async function call_server_method(method_name, arg_object={}) { + let id = `${new Date().getTime()}`; + console.debug(JSON.stringify({ + "id": id, + "method": method_name, + "args": arg_object + })); + return new Promise((resolve, reject) => { + method_call_ev_target.addEventListener(`${id}`, function (event) { + if (event.data.success) resolve(event.data.result); + else reject(event.data.result); + }); + }); +} + +async function fetch_nocors(url, request={}) { + let args = { method: "POST", headers: {}, body: "" }; + request = {...args, ...request}; + request.url = url; + return await call_server_method("http_request", request); +} + +async function call_plugin_method(method_name, arg_object={}) { + if (plugin_name == undefined) + throw new Error("Plugin methods can only be called from inside plugins (duh)"); + return await call_server_method("plugin_method", { + 'plugin_name': plugin_name, + 'method_name': method_name, + 'args': arg_object + }); +}
\ No newline at end of file diff --git a/plugin_loader/static/plugin_page.js b/plugin_loader/static/plugin_page.js new file mode 100644 index 00000000..c58188db --- /dev/null +++ b/plugin_loader/static/plugin_page.js @@ -0,0 +1,44 @@ +(function () { + const PLUGIN_ICON = ` + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plugin" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M1 8a7 7 0 1 1 2.898 5.673c-.167-.121-.216-.406-.002-.62l1.8-1.8a3.5 3.5 0 0 0 + 4.572-.328l1.414-1.415a.5.5 0 0 0 0-.707l-.707-.707 1.559-1.563a.5.5 0 1 0-.708-.706l-1.559 1.562-1.414-1.414 + 1.56-1.562a.5.5 0 1 0-.707-.706l-1.56 1.56-.707-.706a.5.5 0 0 0-.707 0L5.318 5.975a3.5 3.5 0 0 0-.328 + 4.571l-1.8 1.8c-.58.58-.62 1.6.121 2.137A8 8 0 1 0 0 8a.5.5 0 0 0 1 0Z"/> + </svg> + `; + + function createTitle(text) { + return `<div id="plugin_title" class="quickaccessmenu_Title_34nl5">${text}</div>`; + } + + function createPluginList() { + let pages = document.getElementsByClassName("quickaccessmenu_AllTabContents_2yKG4 quickaccessmenu_Down_3rR0o")[0]; + let pluginPage = pages.children[pages.children.length - 1]; + pluginPage.innerHTML = createTitle("Plugins"); + + pluginPage.innerHTML += `<iframe id="plugin_iframe" style="border: none; width: 100%; height: 100%;" src="http://127.0.0.1:1337/plugins/iframe"></iframe>`; + } + + function inject() { + let tabs = document.getElementsByClassName("quickaccessmenu_TabContentColumn_2z5NL Panel Focusable")[0]; + tabs.children[tabs.children.length - 1].innerHTML = PLUGIN_ICON; + + createPluginList(); + } + + let injector = setInterval(function () { + if (document.hasFocus()) { + inject(); + document.getElementById("plugin_title").onclick = function() { + document.getElementById("plugin_iframe").contentWindow.location.href = "http://127.0.0.1:1337/plugins/iframe"; + } + clearInterval(injector); + } + }, 100); +})(); + +function resolveMethodCall(call_id, result) { + let iframe = document.getElementById("plugin_iframe").contentWindow; + iframe.postMessage({'call_id': call_id, 'result': result}, "http://127.0.0.1:1337"); +}
\ No newline at end of file diff --git a/plugin_loader/templates/plugin_view.html b/plugin_loader/templates/plugin_view.html new file mode 100644 index 00000000..8a482fae --- /dev/null +++ b/plugin_loader/templates/plugin_view.html @@ -0,0 +1,31 @@ +<link rel="stylesheet" href="/steam_resource/css/2.css"> +<link rel="stylesheet" href="/steam_resource/css/39.css"> +<link rel="stylesheet" href="/steam_resource/css/library.css"> + +{% if not plugins|length %} +<div class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable" style="--indent-level:0;"> + <div class="basicdialog_FieldChildren_279n8" style="color: white; font-size: large; padding-top: 10px;"> + No plugins installed :( + </div> +</div> +{% endif %} + +<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable"> + <div class="quickaccesscontrols_PanelSectionRow_26R5w"> + {% for plugin in plugins %} + {% if plugin.tile_view_html|length %} + <div onclick="location.href = '/plugins/load/{{ plugin.name }}'" class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable" style="--indent-level:0;"> + {{ plugin.tile_view_html }} + </div> + {% else %} + <div class="quickaccesscontrols_PanelSectionRow_26R5w"> + <div onclick="location.href = '/plugins/load/{{ plugin.name }}'" class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable" style="--indent-level:0;"> + <div class="basicdialog_FieldChildren_279n8"> + <button type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable">{{ plugin.name }}</button> + </div> + </div> + </div> + {% endif %} + {% endfor %} + </div> +</div>
\ No newline at end of file diff --git a/plugin_loader/utilities.py b/plugin_loader/utilities.py new file mode 100644 index 00000000..4e9a1ac0 --- /dev/null +++ b/plugin_loader/utilities.py @@ -0,0 +1,18 @@ +from aiohttp import ClientSession + +async def http_request(method="", url="", **kwargs): + async with ClientSession() as web: + res = await web.request(method, url, **kwargs) + return { + "status": res.status, + "headers": dict(res.headers), + "body": await res.text() + } + +async def ping(**kwargs): + return "pong" + +util_methods = { + "ping": ping, + "http_request": http_request +}
\ No newline at end of file diff --git a/plugin_manager.service b/plugin_manager.service deleted file mode 100644 index fdf3966e..00000000 --- a/plugin_manager.service +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=SteamDeck Plugin Manager - -[Service] -ExecStart=/home/deck/homebrew/services/plugin_manager/plugin_manager -WorkingDirectory=/home/deck/homebrew/services/plugin_manager/ - -[Install] -WantedBy=multi-user.target
\ No newline at end of file diff --git a/plugin_template.py b/plugin_template.py new file mode 100644 index 00000000..be477d37 --- /dev/null +++ b/plugin_template.py @@ -0,0 +1,14 @@ +class Plugin: + name = "Template Plugin" + + author = "SteamDeckHomebrew" + + main_view_html = "<html><body><h3>Template Plugin</h3></body></html>" + + tile_view_html = "" + + async def method_1(**kwargs): + pass + + async def method_2(**kwargs): + pass
\ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..513ec897 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +aiohttp==3.8.1 +aiohttp-jinja2==1.5.0
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 36143ccc..00000000 --- a/src/main.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; -use std::fs; -use hyper::{Client, Uri}; -use hyper::body::Buf; -use serde::{ Serialize, Deserialize }; -use serde_json; -use tungstenite::Message; - -type TokioResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; - -enum AppError { - ContentNotFound -} - -impl Debug for AppError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let message = match self { - AppError::ContentNotFound => "Content not found" - }; - - write!(f, "{}", message) - } -} - -impl Display for AppError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self) - } -} - -unsafe impl Send for AppError { } -unsafe impl Sync for AppError { } - -impl std::error::Error for AppError { } - -#[allow(non_snake_case)] -#[allow(dead_code)] -#[derive(Deserialize)] -struct WebContent { - description: String, - devtoolsFrontendUrl: String, - id: String, - title: String, - r#type: String, - url: String, - webSocketDebuggerUrl: String -} - -#[allow(non_snake_case)] -#[allow(dead_code)] -#[derive(Serialize)] -struct DebuggerCommandParams { - expression: String, - userGesture: bool -} - -#[allow(non_snake_case)] -#[allow(dead_code)] -#[derive(Serialize)] -struct DebuggerCommand { - id: u32, - method: String, - params: DebuggerCommandParams -} - -/// Downloads all content from a website -async fn get_web_content(url: Uri) -> TokioResult<Vec<WebContent>> { - let client = Client::new(); - let response = client.get(url).await?; - let body = hyper::body::aggregate(response).await?; - - let data = String::from(std::str::from_utf8(body.chunk())?); - - Ok(serde_json::from_str(data.as_str())?) -} - -/// Loads plugins -fn load_plugins() -> String { - let paths = fs::read_dir("./plugins"); - if let Ok(paths) = paths { - - let mut result = String::new(); - - for entry in paths { - if let Ok(entry) = entry { - if let Ok(file_type) = entry.file_type() { - if file_type.is_file() { - if let Ok(content) = fs::read_to_string(entry.path()) { - result.push_str(format!("plugins.push(new {});", content).as_str()); - } - } - } - } - } - - result - } else { - String::from("") - } -} - -#[tokio::main] -async fn main() -> TokioResult<()> { - // If CEF Debugging is enabled, it will be accessible through port 8080 - let url = "http://127.0.0.1:8080/json".parse::<hyper::Uri>().unwrap(); - - // Load all available tabs that can be debugged - let contents = get_web_content(url).await?; - - // Find QuickAccess tab (sidebar menu) and get the debugger websocket interface url - let mut quick_access_debug_url: Option<String> = None; - for content in &contents { - if content.title == "QuickAccess" { - quick_access_debug_url = Some(content.webSocketDebuggerUrl.clone()); - } - } - - if let Some(url) = quick_access_debug_url { - - // Connect to debugger websocket - let (mut socket, _) = tungstenite::connect(url)?; - - // Create a inject command to send to the debugger - let command = DebuggerCommand { - id: 1, - method: String::from("Runtime.evaluate"), - params: DebuggerCommandParams { - expression: String::from(include_str!("plugin_page.js").replace("{{ PLUGINS }}", load_plugins().as_str())), - userGesture: true - } - }; - - // Send command to debugger - socket.write_message(Message::Text(serde_json::to_string(&command)?))?; - - // Print response - let response = socket.read_message()?; - println!("{}", response); - - socket.close(None)?; - - } else { - return Err(AppError::ContentNotFound.into()); - } - - - Ok(()) -} diff --git a/src/plugin_page.js b/src/plugin_page.js deleted file mode 100644 index 537f53a3..00000000 --- a/src/plugin_page.js +++ /dev/null @@ -1,82 +0,0 @@ -(function () { - let plugins = []; - - {{ PLUGINS }} - - const PLUGIN_ICON = ` - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plugin" viewBox="0 0 16 16"> - <path fill-rule="evenodd" d="M1 8a7 7 0 1 1 2.898 5.673c-.167-.121-.216-.406-.002-.62l1.8-1.8a3.5 3.5 0 0 0 - 4.572-.328l1.414-1.415a.5.5 0 0 0 0-.707l-.707-.707 1.559-1.563a.5.5 0 1 0-.708-.706l-1.559 1.562-1.414-1.414 - 1.56-1.562a.5.5 0 1 0-.707-.706l-1.56 1.56-.707-.706a.5.5 0 0 0-.707 0L5.318 5.975a3.5 3.5 0 0 0-.328 - 4.571l-1.8 1.8c-.58.58-.62 1.6.121 2.137A8 8 0 1 0 0 8a.5.5 0 0 0 1 0Z"/> - </svg> - `; - - function createTitle(text) { - return `<div class="quickaccessmenu_Title_34nl5">${text}</div>`; - } - - function createTabGroupPanel(content) { - return `<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">${content}</div>`; - } - - function createPanelSelection(content) { - return `<div class="quickaccesscontrols_PanelSection_Ob5uo">${content}</div>`; - } - - function createPanelSelectionRow(content) { - return `<div class="quickaccesscontrols_PanelSectionRow_26R5w">${content}</div>`; - } - - function createButton(text, id) { - return ` - <div class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable" style="--indent-level:0;"> - <div class="basicdialog_FieldChildren_279n8"> - <button id="${id}" type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable">${text}</button> - </div> - </div> - `; - } - - function createPluginList() { - let pages = document.getElementsByClassName("quickaccessmenu_AllTabContents_2yKG4 quickaccessmenu_Down_3rR0o")[0]; - let pluginPage = pages.children[pages.children.length - 1]; - - pluginPage.innerHTML = createTitle("Plugins"); - - let buttons = ""; - for (let i = 0; i < plugins.length; i++) { - buttons += createPanelSelectionRow(createButton(plugins[i].getName(), "plugin_btn_" + i)) - } - - pluginPage.innerHTML += createTabGroupPanel(createPanelSelection(buttons)); - - for (let i = 0; i < plugins.length; i++) { - document.getElementById("plugin_btn_" + i).onclick = (function(plugin, pluginPage) { - return function() { - pluginPage.innerHTML = createButton("Back", "plugin_back") + createTitle(plugin.getName()) + createTabGroupPanel(plugin.getPageContent()); - plugin.runCode(); - - document.getElementById("plugin_back").onclick = (e) => { - createPluginList(); - }; - }; - }(plugins[i], pluginPage)) - } - - } - - function inject() { - let tabs = document.getElementsByClassName("quickaccessmenu_TabContentColumn_2z5NL Panel Focusable")[0]; - tabs.children[tabs.children.length - 1].innerHTML = PLUGIN_ICON; - - createPluginList(); - } - - let injector = setInterval(function () { - if (document.hasFocus()) { - inject(); - clearInterval(injector); - } - }, 100); -})();
\ No newline at end of file diff --git a/template_plugin.js b/template_plugin.js deleted file mode 100644 index 9eb63118..00000000 --- a/template_plugin.js +++ /dev/null @@ -1,15 +0,0 @@ -class Plugin { - constructor() { } - - getName() { - return "Template Plugin"; - } - - getPageContent() { - return "Add your own elements here"; - } - - runCode() { - console.log("Template Plugin loaded"); - } - }
\ No newline at end of file |
