summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormarios <marios8543@gmail.com>2022-04-03 23:50:26 +0300
committerGitHub <noreply@github.com>2022-04-03 23:50:26 +0300
commit5e9c12bac838730d4e216b3779227a9a94447e40 (patch)
tree61f50207f0d45f6fdab09c31d2b35778df8aff63
parentfb6f55a44deef64a0efff9cc645368b946ea897d (diff)
downloaddecky-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/config5
-rw-r--r--.gitignore154
-rw-r--r--.idea/.gitignore8
-rw-r--r--.idea/SteamOS-Plugin-Manager.iml11
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/vcs.xml6
-rw-r--r--Cargo.lock843
-rw-r--r--Cargo.toml13
-rw-r--r--README.md12
-rw-r--r--install.sh37
-rw-r--r--plugin_loader.service13
-rw-r--r--plugin_loader/injector.py85
-rw-r--r--plugin_loader/loader.py60
-rw-r--r--plugin_loader/main.py77
-rw-r--r--plugin_loader/static/library.js41
-rw-r--r--plugin_loader/static/plugin_page.js44
-rw-r--r--plugin_loader/templates/plugin_view.html31
-rw-r--r--plugin_loader/utilities.py18
-rw-r--r--plugin_manager.service9
-rw-r--r--plugin_template.py14
-rw-r--r--requirements.txt2
-rw-r--r--src/main.rs148
-rw-r--r--src/plugin_page.js82
-rw-r--r--template_plugin.js15
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
diff --git a/.gitignore b/.gitignore
index ea8c4bf7..d550eaae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.md b/README.md
index 71eb37c7..56e7d65d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+# This is a Work-In-Progress (WIP)
# SteamOS Plugin Manager
![steamuserimages-a akamaihd](https://user-images.githubusercontent.com/10835354/161068262-ca723dc5-6795-417a-80f6-d8c1f9d03e93.jpg)
@@ -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