mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-13 04:05:04 +03:00
Feat: Disable plugins (#850)
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
Builder Win / Build PluginLoader for Win (push) Has been cancelled
Builder / Build PluginLoader (push) Has been cancelled
Push Updated Plugin Stub to Template / copy-stub (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Type Check / Run type checkers (push) Has been cancelled
* implement base frontend changes necessary for plugin disabling * implement frontend diisable functions/ modal * plugin disable boilerplate / untested * Feat disable plugins (#810) * implement base frontend changes necessary for plugin disabling * implement frontend diisable functions/ modal --------- Co-authored-by: Jesse Bofill <jesse_bofill@yahoo.com> * fix mistakes * add frontend * working plugin disable, not tested extensively * fix uninstalled hidden plugins remaining in list * hide plugin irrelevant plugin setting menu option when disabled * fix hidden plugin issues * reset disabled plugin on uninstall * fix plugin load on reenable * move disable settings uninstall cleanup * add engilsh tranlsations for enable/ disable elements * fix bug where wrong loadType can get passed to importPlugin * show correct number of hidden plugins if plugin is both hidden and disabled * fix: get fresh list of plugin updates when changed in settings plugin list * fix: fix invalid semver plugin version from preventing latest updates * retain x position when changing focus in list items that have multiple horizontal focusables * correction to pluging version checking validation * make sure disabled plugins get checked for updates * show number of disabled plugins at bottom of plugin view * add notice to update modals that disabled plugins will be enabled upon installation * run formatter * Update backend/decky_loader/locales/en-US.json Co-authored-by: EMERALD <hudson.samuels@gmail.com> * chore: correct filename typo * chore: change disabled icon * chore: revert accidental defsettings changes * format * add timeout to frontend importPlugin if a request hangs this prevent it from blocking other plugin loads. backend diaptch_plugin which calls this for individual plugin load (as opposed to batch) is set to 15s. other callers of importPlugin are not using timeout, same as before. * fix plugin update checking loop --------- Co-authored-by: marios <marios8543@gmail.com> Co-authored-by: EMERALD <hudson.samuels@gmail.com>
This commit is contained in:
@@ -150,6 +150,7 @@ class PluginBrowser:
|
||||
# plugins_snapshot = self.plugins.copy()
|
||||
# snapshot_string = pformat(plugins_snapshot)
|
||||
# logger.debug("current plugins: %s", snapshot_string)
|
||||
|
||||
if name in self.plugins:
|
||||
logger.debug("Plugin %s was found", name)
|
||||
await self.plugins[name].stop(uninstall=True)
|
||||
@@ -345,5 +346,10 @@ class PluginBrowser:
|
||||
if name in plugin_order:
|
||||
plugin_order.remove(name)
|
||||
self.settings.setSetting("pluginOrder", plugin_order)
|
||||
|
||||
disabled_plugins: List[str] = self.settings.getSetting("disabled_plugins", [])
|
||||
if name in disabled_plugins:
|
||||
disabled_plugins.remove(name)
|
||||
self.settings.setSetting("disabled_plugins", disabled_plugins)
|
||||
|
||||
logger.debug("Removed any settings for plugin %s", name)
|
||||
|
||||
@@ -78,6 +78,7 @@ class Loader:
|
||||
self.live_reload = live_reload
|
||||
self.reload_queue: ReloadQueue = Queue()
|
||||
self.loop.create_task(self.handle_reloads())
|
||||
self.context: PluginManager = server_instance
|
||||
|
||||
if live_reload:
|
||||
self.observer = Observer()
|
||||
@@ -130,7 +131,7 @@ class Loader:
|
||||
|
||||
async def get_plugins(self):
|
||||
plugins = list(self.plugins.values())
|
||||
return [{"name": str(i), "version": i.version, "load_type": i.load_type} for i in plugins]
|
||||
return [{"name": str(i), "version": i.version, "load_type": i.load_type, "disabled": i.disabled} for i in plugins]
|
||||
|
||||
async def handle_plugin_dist(self, request: web.Request):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
@@ -164,6 +165,10 @@ class Loader:
|
||||
await self.ws.emit(f"loader/plugin_event", {"plugin": plugin.name, "event": event, "args": args})
|
||||
|
||||
plugin = PluginWrapper(file, plugin_directory, self.plugin_path, plugin_emitted_event)
|
||||
if hasattr(self.context, "utilities") and plugin.name in await self.context.utilities.get_setting("disabled_plugins",[]):
|
||||
plugin.disabled = True
|
||||
self.plugins[plugin.name] = plugin
|
||||
return
|
||||
if plugin.name in self.plugins:
|
||||
if not "debug" in plugin.flags and refresh:
|
||||
self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
|
||||
@@ -183,7 +188,7 @@ class Loader:
|
||||
print_exc()
|
||||
|
||||
async def dispatch_plugin(self, name: str, version: str | None, load_type: int = PluginLoadType.ESMODULE_V1.value):
|
||||
await self.ws.emit("loader/import_plugin", name, version, load_type)
|
||||
await self.ws.emit("loader/import_plugin", name, version, load_type, True, 15000)
|
||||
|
||||
async def import_plugins(self):
|
||||
self.logger.info(f"import plugins from {self.plugin_path}")
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
},
|
||||
"no_hash": "This plugin does not have a hash, you are installing it at your own risk.",
|
||||
"not_installed": "(not installed)",
|
||||
"disabled": "The plugin will be re-enabled after installation",
|
||||
"overwrite": {
|
||||
"button_idle": "Overwrite",
|
||||
"button_processing": "Overwriting",
|
||||
@@ -133,10 +134,13 @@
|
||||
"uninstall": "Uninstall",
|
||||
"update_all_one": "Update 1 plugin",
|
||||
"update_all_other": "Update {{count}} plugins",
|
||||
"update_to": "Update to {{name}}"
|
||||
"update_to": "Update to {{name}}",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable"
|
||||
},
|
||||
"PluginListLabel": {
|
||||
"hidden": "Hidden from the quick access menu"
|
||||
"hidden": "Hidden from the quick access menu",
|
||||
"disabled": "Plugin disabled"
|
||||
},
|
||||
"PluginLoader": {
|
||||
"decky_title": "Decky",
|
||||
@@ -152,12 +156,23 @@
|
||||
"desc": "Are you sure you want to uninstall {{name}}?",
|
||||
"title": "Uninstall {{name}}"
|
||||
},
|
||||
"plugin_disable": {
|
||||
"button": "Disable",
|
||||
"desc": "Are you sure you want to disable {{name}}?",
|
||||
"title": "Disable {{name}}",
|
||||
"error": "Error disabling {{name}}"
|
||||
},
|
||||
"plugin_enable": {
|
||||
"error": "Error enabling {{name}}"
|
||||
},
|
||||
"plugin_update_one": "Updates available for 1 plugin!",
|
||||
"plugin_update_other": "Updates available for {{count}} plugins!"
|
||||
},
|
||||
"PluginView": {
|
||||
"hidden_one": "1 plugin is hidden from this list",
|
||||
"hidden_other": "{{count}} plugins are hidden from this list"
|
||||
"hidden_other": "{{count}} plugins are hidden from this list",
|
||||
"disabled_one": "1 plugin is disabled",
|
||||
"disabled_other": "{{count}} plugins are disabled"
|
||||
},
|
||||
"RemoteDebugging": {
|
||||
"remote_cef": {
|
||||
|
||||
@@ -41,6 +41,7 @@ class PluginWrapper:
|
||||
self.author = json["author"]
|
||||
self.flags = json["flags"]
|
||||
self.api_version = json["api_version"] if "api_version" in json else 0
|
||||
self.disabled = False
|
||||
|
||||
self.passive = not path.isfile(self.file)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from os import stat_result
|
||||
from os import path, stat_result
|
||||
import uuid
|
||||
from urllib.parse import unquote
|
||||
from json.decoder import JSONDecodeError
|
||||
@@ -8,7 +8,7 @@ import re
|
||||
from traceback import format_exc
|
||||
from stat import FILE_ATTRIBUTE_HIDDEN # pyright: ignore [reportAttributeAccessIssue, reportUnknownVariableType]
|
||||
|
||||
from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection
|
||||
from asyncio import StreamReader, StreamWriter, sleep, start_server, gather, open_connection
|
||||
from aiohttp import ClientSession, hdrs
|
||||
from aiohttp.web import Request, StreamResponse, Response, json_response, post
|
||||
from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict
|
||||
@@ -80,6 +80,8 @@ class Utilities:
|
||||
context.ws.add_route("utilities/restart_webhelper", self.restart_webhelper)
|
||||
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
|
||||
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)
|
||||
context.ws.add_route("utilities/enable_plugin", self.enable_plugin)
|
||||
context.ws.add_route("utilities/disable_plugin", self.disable_plugin)
|
||||
|
||||
context.web_app.add_routes([
|
||||
post("/methods/{method_name}", self._handle_legacy_server_method_call)
|
||||
@@ -214,7 +216,7 @@ class Utilities:
|
||||
|
||||
async def http_request_legacy(self, method: str, url: str, extra_opts: Any = {}, timeout: int | None = None):
|
||||
async with ClientSession() as web:
|
||||
res = await web.request(method, url, ssl=helpers.get_ssl_context(), timeout=timeout, **extra_opts)
|
||||
res = await web.request(method, url, ssl=helpers.get_ssl_context(), timeout=timeout, **extra_opts) # type: ignore
|
||||
text = await res.text()
|
||||
return {
|
||||
"status": res.status,
|
||||
@@ -390,7 +392,6 @@ class Utilities:
|
||||
"total": len(all),
|
||||
}
|
||||
|
||||
|
||||
# Based on https://stackoverflow.com/a/46422554/13174603
|
||||
def start_rdt_proxy(self, ip: str, port: int):
|
||||
async def pipe(reader: StreamReader, writer: StreamWriter):
|
||||
@@ -474,3 +475,32 @@ class Utilities:
|
||||
|
||||
async def get_tab_id(self, name: str):
|
||||
return (await get_tab(name)).id
|
||||
|
||||
async def disable_plugin(self, name: str):
|
||||
disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])
|
||||
if name not in disabled_plugins:
|
||||
disabled_plugins.append(name)
|
||||
await self.set_setting("disabled_plugins", disabled_plugins)
|
||||
|
||||
await self.context.plugin_loader.plugins[name].stop()
|
||||
await self.context.ws.emit("loader/disable_plugin", name)
|
||||
|
||||
async def enable_plugin(self, name: str):
|
||||
plugin_folder = self.context.plugin_browser.find_plugin_folder(name)
|
||||
assert plugin_folder is not None
|
||||
plugin_dir = path.join(self.context.plugin_browser.plugin_path, plugin_folder)
|
||||
|
||||
if name in self.context.plugin_loader.plugins:
|
||||
plugin = self.context.plugin_loader.plugins[name]
|
||||
if plugin.proc and plugin.proc.is_alive():
|
||||
await plugin.stop()
|
||||
self.context.plugin_loader.plugins.pop(name, None)
|
||||
await sleep(1)
|
||||
|
||||
disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])
|
||||
|
||||
if name in disabled_plugins:
|
||||
disabled_plugins.remove(name)
|
||||
await self.set_setting("disabled_plugins", disabled_plugins)
|
||||
|
||||
await self.context.plugin_loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
|
||||
Reference in New Issue
Block a user