diff options
| -rw-r--r-- | backend/browser.py | 97 | ||||
| -rw-r--r-- | frontend/src/components/modals/PluginInstallModal.tsx | 8 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/index.tsx | 10 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/developer/index.tsx | 137 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/general/index.tsx | 21 |
5 files changed, 163 insertions, 110 deletions
diff --git a/backend/browser.py b/backend/browser.py index 83fb3426..74354edf 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -139,6 +139,10 @@ class PluginBrowser: self.loader.watcher.disabled = False async def _install(self, artifact, name, version, hash): + # Will be set later in code + res_zip = None + + # Check if plugin is installed isInstalled = False if self.loader.watcher: self.loader.watcher.disabled = True @@ -148,47 +152,62 @@ class PluginBrowser: isInstalled = True except: logger.error(f"Failed to determine if {name} is already installed, continuing anyway.") - logger.info(f"Installing {name} (Version: {version})") - async with ClientSession() as client: - logger.debug(f"Fetching {artifact}") - res = await client.get(artifact, ssl=get_ssl_context()) - if res.status == 200: - logger.debug("Got 200. Reading...") - data = await res.read() - logger.debug(f"Read {len(data)} bytes") - res_zip = BytesIO(data) - if isInstalled: - try: - logger.debug("Uninstalling existing plugin...") - await self.uninstall_plugin(name) - except: - logger.error(f"Plugin {name} could not be uninstalled.") - logger.debug("Unzipping...") - ret = self._unzip_to_plugin_dir(res_zip, name, hash) - if ret: - plugin_folder = self.find_plugin_folder(name) - plugin_dir = path.join(self.plugin_path, plugin_folder) - ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) - if ret: - logger.info(f"Installed {name} (Version: {version})") - if name in self.loader.plugins: - self.loader.plugins[name].stop() - self.loader.plugins.pop(name, None) - await sleep(1) - - current_plugin_order = self.settings.getSetting("pluginOrder") - current_plugin_order.append(name) - self.settings.setSetting("pluginOrder", current_plugin_order) - logger.debug("Plugin %s was added to the pluginOrder setting", name) - self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder) - else: - logger.fatal(f"Failed Downloading Remote Binaries") + + # Check if the file is a local file or a URL + if artifact.startswith("file://"): + logger.info(f"Installing {name} from local ZIP file (Version: {version})") + res_zip = BytesIO(open(artifact[7:], "rb").read()) + else: + logger.info(f"Installing {name} from URL (Version: {version})") + async with ClientSession() as client: + logger.debug(f"Fetching {artifact}") + res = await client.get(artifact, ssl=get_ssl_context()) + if res.status == 200: + logger.debug("Got 200. Reading...") + data = await res.read() + logger.debug(f"Read {len(data)} bytes") + res_zip = BytesIO(data) else: - self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})") - if self.loader.watcher: - self.loader.watcher.disabled = False + logger.fatal(f"Could not fetch from URL. {await res.text()}") + + # Check to make sure we got the file + if res_zip is None: + logger.fatal(f"Could not fetch {artifact}") + return + + # If plugin is installed, uninstall it + if isInstalled: + try: + logger.debug("Uninstalling existing plugin...") + await self.uninstall_plugin(name) + except: + logger.error(f"Plugin {name} could not be uninstalled.") + + # Install the plugin + logger.debug("Unzipping...") + ret = self._unzip_to_plugin_dir(res_zip, name, hash) + if ret: + plugin_folder = self.find_plugin_folder(name) + plugin_dir = path.join(self.plugin_path, plugin_folder) + ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) + if ret: + logger.info(f"Installed {name} (Version: {version})") + if name in self.loader.plugins: + self.loader.plugins[name].stop() + self.loader.plugins.pop(name, None) + await sleep(1) + + current_plugin_order = self.settings.getSetting("pluginOrder") + current_plugin_order.append(name) + self.settings.setSetting("pluginOrder", current_plugin_order) + logger.debug("Plugin %s was added to the pluginOrder setting", name) + self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder) else: - logger.fatal(f"Could not fetch from URL. {await res.text()}") + logger.fatal(f"Failed Downloading Remote Binaries") + else: + self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})") + if self.loader.watcher: + self.loader.watcher.disabled = False async def request_plugin_install(self, artifact, name, version, hash): request_id = str(time()) diff --git a/frontend/src/components/modals/PluginInstallModal.tsx b/frontend/src/components/modals/PluginInstallModal.tsx index f2f13bbf..7f0683ee 100644 --- a/frontend/src/components/modals/PluginInstallModal.tsx +++ b/frontend/src/components/modals/PluginInstallModal.tsx @@ -29,10 +29,10 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha strTitle={`Install ${artifact}`} strOKButtonText={loading ? 'Installing' : 'Install'} > - {hash == 'False' ? ( - <h3 style={{ color: 'red' }}>!!!!NO HASH PROVIDED!!!!</h3> - ) : ( - `Are you sure you want to install ${artifact} ${version}?` + Are you sure you want to install {artifact} + {version ? ` ${version}` : ''}? + {hash == 'False' && ( + <span style={{ color: 'red' }}> This plugin does not have a hash, you are installing it at your own risk.</span> )} </ConfirmModal> ); diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx index dcf179a3..ec3fc260 100644 --- a/frontend/src/components/modals/filepicker/index.tsx +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -134,7 +134,15 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({ )} </div> )} - {file.name} + <span + style={{ + textOverflow: 'ellipsis', + overflow: 'hidden', + textAlign: 'left', + }} + > + {file.name} + </span> </div> </DialogButton> ); diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx index bd80cb77..e6e37813 100644 --- a/frontend/src/components/settings/pages/developer/index.tsx +++ b/frontend/src/components/settings/pages/developer/index.tsx @@ -1,64 +1,109 @@ -import { DialogBody, Field, TextField, Toggle } from 'decky-frontend-lib'; -import { useRef } from 'react'; -import { FaReact, FaSteamSymbol } from 'react-icons/fa'; +import { + DialogBody, + DialogButton, + DialogControlsSection, + DialogControlsSectionHeader, + Field, + TextField, + Toggle, +} from 'decky-frontend-lib'; +import { useRef, useState } from 'react'; +import { FaFileArchive, FaLink, FaReact, FaSteamSymbol } from 'react-icons/fa'; import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer'; +import { installFromURL } from '../../../../store'; import { useSetting } from '../../../../utils/hooks/useSetting'; import RemoteDebuggingSettings from '../general/RemoteDebugging'; +const installFromZip = () => { + window.DeckyPluginLoader.openFilePicker('/home/deck', true).then((val) => { + const url = `file://${val.path}`; + console.log(`Installing plugin locally from ${url}`); + + if (url.endsWith('.zip')) { + installFromURL(url); + } else { + window.DeckyPluginLoader.toaster.toast({ + title: 'Decky', + body: `Installation failed! Only ZIP files are supported.`, + onClick: installFromZip, + }); + } + }); +}; + export default function DeveloperSettings() { const [enableValveInternal, setEnableValveInternal] = useSetting<boolean>('developer.valve_internal', false); const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false); const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', ''); + const [pluginURL, setPluginURL] = useState(''); const textRef = useRef<HTMLDivElement>(null); return ( <DialogBody> - <RemoteDebuggingSettings /> - <Field - label="Enable Valve Internal" - description={ - <span style={{ whiteSpace: 'pre-line' }}> - Enables the Valve internal developer menu.{' '} - <span style={{ color: 'red' }}>Do not touch anything in this menu unless you know what it does.</span> - </span> - } - icon={<FaSteamSymbol style={{ display: 'block' }} />} - > - <Toggle - value={enableValveInternal} - onChange={(toggleValue) => { - setEnableValveInternal(toggleValue); - setShowValveInternal(toggleValue); - }} - /> - </Field> - <Field - label="Enable React DevTools" - description={ - <> + <DialogControlsSection> + <DialogControlsSectionHeader>Third-Party Plugins</DialogControlsSectionHeader> + <Field label="Install Plugin from ZIP File" icon={<FaFileArchive style={{ display: 'block' }} />}> + <DialogButton onClick={installFromZip}>Browse</DialogButton> + </Field> + <Field + label="Install Plugin from URL" + description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />} + icon={<FaLink style={{ display: 'block' }} />} + > + <DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}> + Install + </DialogButton> + </Field> + </DialogControlsSection> + <DialogControlsSection> + <DialogControlsSectionHeader>Other</DialogControlsSectionHeader> + <RemoteDebuggingSettings /> + <Field + label="Enable Valve Internal" + description={ <span style={{ whiteSpace: 'pre-line' }}> - Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the - IP address before enabling. + Enables the Valve internal developer menu.{' '} + <span style={{ color: 'red' }}>Do not touch anything in this menu unless you know what it does.</span> </span> - <br /> - <br /> - <div ref={textRef}> - <TextField label={'IP'} value={reactDevtoolsIP} onChange={(e) => setReactDevtoolsIP(e?.target.value)} /> - </div> - </> - } - icon={<FaReact style={{ display: 'block' }} />} - > - <Toggle - value={reactDevtoolsEnabled} - // disabled={reactDevtoolsIP == ''} - onChange={(toggleValue) => { - setReactDevtoolsEnabled(toggleValue); - setShouldConnectToReactDevTools(toggleValue); - }} - /> - </Field> + } + icon={<FaSteamSymbol style={{ display: 'block' }} />} + > + <Toggle + value={enableValveInternal} + onChange={(toggleValue) => { + setEnableValveInternal(toggleValue); + setShowValveInternal(toggleValue); + }} + /> + </Field> + <Field + label="Enable React DevTools" + description={ + <> + <span style={{ whiteSpace: 'pre-line' }}> + Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set + the IP address before enabling. + </span> + <br /> + <br /> + <div ref={textRef}> + <TextField label={'IP'} value={reactDevtoolsIP} onChange={(e) => setReactDevtoolsIP(e?.target.value)} /> + </div> + </> + } + icon={<FaReact style={{ display: 'block' }} />} + > + <Toggle + value={reactDevtoolsEnabled} + // disabled={reactDevtoolsIP == ''} + onChange={(toggleValue) => { + setReactDevtoolsEnabled(toggleValue); + setShouldConnectToReactDevTools(toggleValue); + }} + /> + </Field> + </DialogControlsSection> </DialogBody> ); } diff --git a/frontend/src/components/settings/pages/general/index.tsx b/frontend/src/components/settings/pages/general/index.tsx index e0bd9691..97fd3e42 100644 --- a/frontend/src/components/settings/pages/general/index.tsx +++ b/frontend/src/components/settings/pages/general/index.tsx @@ -1,15 +1,5 @@ -import { - DialogBody, - DialogButton, - DialogControlsSection, - DialogControlsSectionHeader, - Field, - TextField, - Toggle, -} from 'decky-frontend-lib'; -import { useState } from 'react'; +import { DialogBody, DialogControlsSection, DialogControlsSectionHeader, Field, Toggle } from 'decky-frontend-lib'; -import { installFromURL } from '../../../../store'; import { useDeckyState } from '../../../DeckyState'; import BranchSelect from './BranchSelect'; import StoreSelect from './StoreSelect'; @@ -22,7 +12,6 @@ export default function GeneralSettings({ isDeveloper: boolean; setIsDeveloper: (val: boolean) => void; }) { - const [pluginURL, setPluginURL] = useState(''); const { versionInfo } = useDeckyState(); return ( @@ -46,14 +35,6 @@ export default function GeneralSettings({ }} /> </Field> - <Field - label="Install plugin from URL" - description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />} - > - <DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}> - Install - </DialogButton> - </Field> </DialogControlsSection> <DialogControlsSection> <DialogControlsSectionHeader>About</DialogControlsSectionHeader> |
