Feat: Disable plugins (#850)

* 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:
jbofill
2025-12-30 12:29:08 -07:00
committed by GitHub
parent 789851579b
commit 9f586a1b97
19 changed files with 378 additions and 79 deletions
+34 -4
View 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)