summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/browser.py97
-rw-r--r--frontend/src/components/modals/PluginInstallModal.tsx8
-rw-r--r--frontend/src/components/modals/filepicker/index.tsx10
-rw-r--r--frontend/src/components/settings/pages/developer/index.tsx137
-rw-r--r--frontend/src/components/settings/pages/general/index.tsx21
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>