From 9f586a1b97cf9069fbfbeee17e3909baf9e95f66 Mon Sep 17 00:00:00 2001 From: jbofill <74568881+jessebofill@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:29:08 -0700 Subject: 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 * 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 * 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 Co-authored-by: EMERALD --- backend/decky_loader/utilities.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) (limited to 'backend/decky_loader/utilities.py') diff --git a/backend/decky_loader/utilities.py b/backend/decky_loader/utilities.py index 69c69fe6..75593fd5 100644 --- a/backend/decky_loader/utilities.py +++ b/backend/decky_loader/utilities.py @@ -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) \ No newline at end of file -- cgit v1.2.3