summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/decky_loader/utilities.py98
-rw-r--r--backend/decky_loader/wsrouter.py5
-rw-r--r--frontend/src/components/modals/PluginUninstallModal.tsx4
-rw-r--r--frontend/src/components/modals/filepicker/index.tsx90
-rw-r--r--frontend/src/components/settings/pages/developer/index.tsx13
-rw-r--r--frontend/src/components/settings/pages/general/RemoteDebugging.tsx4
-rw-r--r--frontend/src/developer.tsx4
-rw-r--r--frontend/src/index.ts6
-rw-r--r--frontend/src/plugin-loader.tsx36
-rw-r--r--frontend/src/plugin.ts24
-rw-r--r--frontend/src/start.tsx2
-rw-r--r--frontend/src/store.tsx21
-rw-r--r--frontend/src/utils/settings.ts4
-rw-r--r--frontend/src/wsrouter.ts13
14 files changed, 163 insertions, 161 deletions
diff --git a/backend/decky_loader/utilities.py b/backend/decky_loader/utilities.py
index 20280c24..2eea63ea 100644
--- a/backend/decky_loader/utilities.py
+++ b/backend/decky_loader/utilities.py
@@ -66,6 +66,21 @@ class Utilities:
context.ws.add_route("utilities/ping", self.ping)
context.ws.add_route("utilities/settings/get", self.get_setting)
context.ws.add_route("utilities/settings/set", self.set_setting)
+ context.ws.add_route("utilities/install_plugin", self.install_plugin)
+ context.ws.add_route("utilities/install_plugins", self.install_plugins)
+ context.ws.add_route("utilities/cancel_plugin_install", self.cancel_plugin_install)
+ context.ws.add_route("utilities/confirm_plugin_install", self.confirm_plugin_install)
+ context.ws.add_route("utilities/uninstall_plugin", self.uninstall_plugin)
+ context.ws.add_route("utilities/execute_in_tab", self.execute_in_tab)
+ context.ws.add_route("utilities/inject_css_into_tab", self.inject_css_into_tab)
+ context.ws.add_route("utilities/remove_css_from_tab", self.remove_css_from_tab)
+ context.ws.add_route("utilities/allow_remote_debugging", self.allow_remote_debugging)
+ context.ws.add_route("utilities/disallow_remote_debugging", self.disallow_remote_debugging)
+ context.ws.add_route("utilities/filepicker_ls", self.filepicker_ls)
+ context.ws.add_route("utilities/disable_rdt", self.disable_rdt)
+ context.ws.add_route("utilities/enable_rdt", self.enable_rdt)
+ context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
+ context.ws.add_route("utilities/get_user_info", self.get_user_info)
async def _handle_server_method_call(self, request):
method_name = request.match_info["method_name"]
@@ -139,62 +154,39 @@ class Utilities:
"result": e
}
- async def inject_css_into_tab(self, tab: str, style: str):
- try:
- css_id = str(uuid.uuid4())
-
- result = await inject_to_tab(tab,
- f"""
- (function() {{
- const style = document.createElement('style');
- style.id = "{css_id}";
- document.head.append(style);
- style.textContent = `{style}`;
- }})()
- """, False)
-
- if result and "exceptionDetails" in result["result"]:
- return {
- "success": False,
- "result": result["result"]
- }
+ async def inject_css_into_tab(self, tab: str, style: str) -> str:
+ css_id = str(uuid.uuid4())
- return {
- "success": True,
- "result": css_id
- }
- except Exception as e:
- return {
- "success": False,
- "result": e
- }
+ result = await inject_to_tab(tab,
+ f"""
+ (function() {{
+ const style = document.createElement('style');
+ style.id = "{css_id}";
+ document.head.append(style);
+ style.textContent = `{style}`;
+ }})()
+ """, False)
+
+ if "exceptionDetails" in result["result"]:
+ raise result["result"]["exceptionDetails"]
+
+ return css_id
async def remove_css_from_tab(self, tab: str, css_id: str):
- try:
- result = await inject_to_tab(tab,
- f"""
- (function() {{
- let style = document.getElementById("{css_id}");
+ result = await inject_to_tab(tab,
+ f"""
+ (function() {{
+ let style = document.getElementById("{css_id}");
- if (style.nodeName.toLowerCase() == 'style')
- style.parentNode.removeChild(style);
- }})()
- """, False)
+ if (style.nodeName.toLowerCase() == 'style')
+ style.parentNode.removeChild(style);
+ }})()
+ """, False)
- if result and "exceptionDetails" in result["result"]:
- return {
- "success": False,
- "result": result
- }
+ if "exceptionDetails" in result["result"]:
+ raise result["result"]["exceptionDetails"]
- return {
- "success": True
- }
- except Exception as e:
- return {
- "success": False,
- "result": e
- }
+ return
async def get_setting(self, key: str, default: Any):
return self.context.settings.getSetting(key, default)
@@ -211,12 +203,12 @@ class Utilities:
return True
async def filepicker_ls(self,
- path : str | None = None,
+ path: str | None = None,
include_files: bool = True,
include_folders: bool = True,
- include_ext: list[str] = [],
+ include_ext: list[str] | None = None,
include_hidden: bool = False,
- order_by: str = "name_asc",
+ order_by: str = "name_desc",
filter_for: str | None = None,
page: int = 1,
max: int = 1000):
diff --git a/backend/decky_loader/wsrouter.py b/backend/decky_loader/wsrouter.py
index 2b4c3a3b..7a7b59c9 100644
--- a/backend/decky_loader/wsrouter.py
+++ b/backend/decky_loader/wsrouter.py
@@ -79,7 +79,7 @@ class WSRouter:
match data["type"]:
case MessageType.CALL.value:
# do stuff with the message
- if self.routes[data["route"]]:
+ if data["route"] in self.routes:
try:
res = await self.routes[data["route"]](*data["args"])
await self.write({"type": MessageType.REPLY.value, "id": data["id"], "result": res})
@@ -87,7 +87,8 @@ class WSRouter:
except:
await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": format_exc()})
else:
- await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": "Route does not exist."})
+ # Dunno why but fstring doesnt work here
+ await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": "Route " + data["route"] + " does not exist."})
case MessageType.REPLY.value:
if self.running_calls[data["id"]]:
self.running_calls[data["id"]].set_result(data["result"])
diff --git a/frontend/src/components/modals/PluginUninstallModal.tsx b/frontend/src/components/modals/PluginUninstallModal.tsx
index e7ecbc99..e9c243b6 100644
--- a/frontend/src/components/modals/PluginUninstallModal.tsx
+++ b/frontend/src/components/modals/PluginUninstallModal.tsx
@@ -1,6 +1,8 @@
import { ConfirmModal } from 'decky-frontend-lib';
import { FC } from 'react';
+import { uninstallPlugin } from '../../plugin';
+
interface PluginUninstallModalProps {
name: string;
title: string;
@@ -14,7 +16,7 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
<ConfirmModal
closeModal={closeModal}
onOK={async () => {
- await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name });
+ await uninstallPlugin(name);
// uninstalling a plugin resets the hidden setting for it server-side
// we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
await window.DeckyPluginLoader.hiddenPluginsService.invalidate();
diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx
index c4e72d95..2dfa3ccd 100644
--- a/frontend/src/components/modals/filepicker/index.tsx
+++ b/frontend/src/components/modals/filepicker/index.tsx
@@ -95,29 +95,20 @@ const sortOptions = [
},
];
-function getList(
- path: string,
- includeFiles: boolean,
- includeFolders: boolean = true,
- includeExt: string[] | null = null,
- includeHidden: boolean = false,
- orderBy: SortOptions = SortOptions.name_desc,
- filterFor: RegExp | ((file: File) => boolean) | null = null,
- pageNumber: number = 1,
- max: number = 1000,
-): Promise<{ result: FileListing | string; success: boolean }> {
- return window.DeckyPluginLoader.callServerMethod('filepicker_ls', {
- path,
- include_files: includeFiles,
- include_folders: includeFolders,
- include_ext: includeExt ? includeExt : [],
- include_hidden: includeHidden,
- order_by: orderBy,
- filter_for: filterFor,
- page: pageNumber,
- max: max,
- });
-}
+const getList = window.DeckyBackend.callable<
+ [
+ path: string,
+ includeFiles?: boolean,
+ includeFolders?: boolean,
+ includeExt?: string[] | null,
+ includeHidden?: boolean,
+ orderBy?: SortOptions,
+ filterFor?: RegExp | ((file: File) => boolean) | null,
+ pageNumber?: number,
+ max?: number,
+ ],
+ FileListing
+>('utilities/filepicker_ls');
const iconStyles = {
paddingRight: '10px',
@@ -126,20 +117,20 @@ const iconStyles = {
const FilePicker: FunctionComponent<FilePickerProps> = ({
startPath,
- //What are we allowing to show in the file picker
+ // What are we allowing to show in the file picker
includeFiles = true,
includeFolders = true,
- //Parameter for specifying a specific filename match
+ // Parameter for specifying a specific filename match
filter = undefined,
- //Filter for specific extensions as an array
+ // Filter for specific extensions as an array
validFileExtensions = undefined,
- //Allow to override the fixed extension above
+ // Allow to override the fixed extension above
allowAllFiles = true,
- //If we need to show hidden files and folders (both Win and Linux should work)
+ // If we need to show hidden files and folders (both Win and Linux should work)
defaultHidden = false, // false by default makes sense for most users
- //How much files per page to show, default 1000
+ // How many files per page to show, default 1000
max = 1000,
- //Which picking option to select by default
+ // Which picking option to select by default
fileSelType = FileSelectionType.FOLDER,
onSubmit,
closeModal,
@@ -190,21 +181,27 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
useEffect(() => {
(async () => {
setLoading(true);
- const listing = await getList(
- path,
- includeFiles,
- includeFolders,
- selectedExts,
- showHidden,
- sort,
- filter,
- page,
- max,
- );
- if (!listing.success) {
+ try {
+ const listing = await getList(
+ path,
+ includeFiles,
+ includeFolders,
+ selectedExts,
+ showHidden,
+ sort,
+ filter,
+ page,
+ max,
+ );
+ setRawError(null);
+ setError(FileErrorTypes.None);
+ setFiles(listing.files);
+ setLoading(false);
+ setListing(listing);
+ logger.log('reloaded', path, listing);
+ } catch (theError: any) {
setListing({ files: [], realpath: path, total: 0 });
setLoading(false);
- const theError = listing.result as string;
switch (theError) {
case theError.match(/\[Errno\s2.*/i)?.input:
case theError.match(/\[WinError\s3.*/i)?.input:
@@ -220,14 +217,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
}
logger.debug(theError);
return;
- } else {
- setRawError(null);
- setError(FileErrorTypes.None);
- setFiles((listing.result as FileListing).files);
}
- setLoading(false);
- setListing(listing.result as FileListing);
- logger.log('reloaded', path, listing);
})();
}, [error, path, includeFiles, includeFolders, showHidden, sort, selectedExts, page]);
diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx
index 5ed76515..36d3b5c0 100644
--- a/frontend/src/components/settings/pages/developer/index.tsx
+++ b/frontend/src/components/settings/pages/developer/index.tsx
@@ -91,13 +91,16 @@ export default function DeveloperSettings() {
>
<DialogButton
onClick={async () => {
- let res = await window.DeckyPluginLoader.callServerMethod('get_tab_id', { name: 'SharedJSContext' });
- if (res.success) {
+ try {
+ let tabId = await window.DeckyBackend.call<[name: string], string>(
+ 'utilities/get_tab_id',
+ 'SharedJSContext',
+ );
Navigation.NavigateToExternalWeb(
- 'localhost:8080/devtools/inspector.html?ws=localhost:8080/devtools/page/' + res.result,
+ 'localhost:8080/devtools/inspector.html?ws=localhost:8080/devtools/page/' + tabId,
);
- } else {
- console.error('Unable to find ID for SharedJSContext tab ', res.result);
+ } catch (e) {
+ console.error('Unable to find ID for SharedJSContext tab ', e);
Navigation.NavigateToExternalWeb('localhost:8080');
}
}}
diff --git a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
index 60d57d91..60e0e3c1 100644
--- a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
+++ b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
@@ -18,8 +18,8 @@ export default function RemoteDebuggingSettings() {
value={allowRemoteDebugging || false}
onChange={(toggleValue) => {
setAllowRemoteDebugging(toggleValue);
- if (toggleValue) window.DeckyPluginLoader.callServerMethod('allow_remote_debugging');
- else window.DeckyPluginLoader.callServerMethod('disallow_remote_debugging');
+ if (toggleValue) window.DeckyBackend.call('allow_remote_debugging');
+ else window.DeckyBackend.call('disallow_remote_debugging');
}}
/>
</Field>
diff --git a/frontend/src/developer.tsx b/frontend/src/developer.tsx
index 43f550d7..8bd09812 100644
--- a/frontend/src/developer.tsx
+++ b/frontend/src/developer.tsx
@@ -50,9 +50,7 @@ export async function setShouldConnectToReactDevTools(enable: boolean) {
icon: <FaReact />,
});
await sleep(5000);
- return enable
- ? window.DeckyPluginLoader.callServerMethod('enable_rdt')
- : window.DeckyPluginLoader.callServerMethod('disable_rdt');
+ return enable ? window.DeckyBackend.call('utilities/enable_rdt') : window.DeckyBackend.call('utilities/disable_rdt');
}
export async function startup() {
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
index 6588cb5c..3a0c4880 100644
--- a/frontend/src/index.ts
+++ b/frontend/src/index.ts
@@ -2,5 +2,11 @@
(async () => {
console.debug('Setting up decky-frontend-lib...');
window.DFL = await import('decky-frontend-lib');
+ console.debug('Authenticating to Decky backend...');
+ window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
+ console.debug('Connecting to Decky backend...');
+ window.DeckyBackend = new (await import('./wsrouter')).WSRouter();
+ await window.DeckyBackend.connect();
+ console.debug('Starting Decky!');
await import('./start');
})();
diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 86592016..3c9ba818 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -34,7 +34,6 @@ import Toaster from './toaster';
import { VerInfo, callUpdaterMethod } from './updater';
import { getSetting, setSetting } from './utils/settings';
import TranslationHelper, { TranslationClass } from './utils/TranslationHelper';
-import { WSRouter } from './wsrouter';
const StorePage = lazy(() => import('./components/store/Store'));
const SettingsPage = lazy(() => import('./components/settings'));
@@ -49,8 +48,6 @@ class PluginLoader extends Logger {
public toaster: Toaster = new Toaster();
private deckyState: DeckyState = new DeckyState();
- public ws: WSRouter = new WSRouter();
-
public hiddenPluginsService = new HiddenPluginsService(this.deckyState);
public notificationService = new NotificationService(this.deckyState);
@@ -105,15 +102,13 @@ class PluginLoader extends Logger {
initFilepickerPatches();
- this.ws.connect().then(() => {
- this.getUserInfo();
+ this.getUserInfo();
- this.updateVersion();
- });
+ this.updateVersion();
}
public async getUserInfo() {
- const userInfo = (await this.callServerMethod('get_user_info')).result as UserInfo;
+ const userInfo = await window.DeckyBackend.call<[], UserInfo>('utilities/get_user_info');
setSetting('user_info.user_name', userInfo.username);
setSetting('user_info.user_home', userInfo.path);
}
@@ -183,8 +178,8 @@ class PluginLoader extends Logger {
version={version}
hash={hash}
installType={install_type}
- onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })}
- onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })}
+ onOK={() => window.DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)}
+ onCancel={() => window.DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)}
/>,
);
}
@@ -196,8 +191,8 @@ class PluginLoader extends Logger {
showModal(
<MultiplePluginsInstallModal
requests={requests}
- onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })}
- onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })}
+ onOK={() => window.DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)}
+ onCancel={() => window.DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)}
/>,
);
}
@@ -360,6 +355,7 @@ class PluginLoader extends Logger {
selectFiles?: boolean,
regex?: RegExp,
): Promise<{ path: string; realpath: string }> {
+ console.warn('openFilePicker is deprecated and will be removed. Please migrate to openFilePickerV2');
if (selectFiles) {
return this.openFilePickerV2(FileSelectionType.FILE, startPath, true, true, regex);
} else {
@@ -443,18 +439,10 @@ class PluginLoader extends Logger {
code,
});
},
- injectCssIntoTab(tab: string, style: string) {
- return this.callServerMethod('inject_css_into_tab', {
- tab,
- style,
- });
- },
- removeCssFromTab(tab: string, cssId: any) {
- return this.callServerMethod('remove_css_from_tab', {
- tab,
- css_id: cssId,
- });
- },
+ injectCssIntoTab: window.DeckyBackend.callable<[tab: string, style: string], string>(
+ 'utilities/inject_css_into_tab',
+ ),
+ removeCssFromTab: window.DeckyBackend.callable<[tab: string, cssId: string]>('utilities/remove_css_from_tab'),
};
}
}
diff --git a/frontend/src/plugin.ts b/frontend/src/plugin.ts
index fa1bb28a..21d45654 100644
--- a/frontend/src/plugin.ts
+++ b/frontend/src/plugin.ts
@@ -13,3 +13,27 @@ export enum InstallType {
REINSTALL,
UPDATE,
}
+
+type installPluginArgs = [
+ artifact: string,
+ name?: string,
+ version?: string,
+ hash?: string | boolean,
+ installType?: InstallType,
+];
+
+export let installPlugin = window.DeckyBackend.callable<installPluginArgs>('utilities/install_plugin');
+
+type installPluginsArgs = [
+ requests: {
+ artifact: string;
+ name?: string;
+ version?: string;
+ hash?: string | boolean;
+ installType?: InstallType;
+ }[],
+];
+
+export let installPlugins = window.DeckyBackend.callable<installPluginsArgs>('utilities/install_plugins');
+
+export let uninstallPlugin = window.DeckyBackend.callable<[name: string]>('utilities/uninstall_plugin');
diff --git a/frontend/src/start.tsx b/frontend/src/start.tsx
index 94b22ffe..a125e3d4 100644
--- a/frontend/src/start.tsx
+++ b/frontend/src/start.tsx
@@ -19,8 +19,6 @@ declare global {
}
(async () => {
- window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
-
i18n
.use(Backend)
.use(initReactI18next)
diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx
index bc419430..5ae98ec4 100644
--- a/frontend/src/store.tsx
+++ b/frontend/src/store.tsx
@@ -1,4 +1,4 @@
-import { InstallType, Plugin } from './plugin';
+import { InstallType, Plugin, installPlugin, installPlugins } from './plugin';
import { getSetting, setSetting } from './utils/settings';
export enum Store {
@@ -92,33 +92,24 @@ export async function getPluginList(): Promise<StorePlugin[]> {
export async function installFromURL(url: string) {
const splitURL = url.split('/');
- await window.DeckyPluginLoader.callServerMethod('install_plugin', {
- name: splitURL[splitURL.length - 1].replace('.zip', ''),
- artifact: url,
- });
+ await installPlugin(url, splitURL[splitURL.length - 1].replace('.zip', ''));
}
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion, installType: InstallType) {
const artifactUrl = selectedVer.artifact ?? pluginUrl(selectedVer.hash);
- await window.DeckyPluginLoader.callServerMethod('install_plugin', {
- name: plugin,
- artifact: artifactUrl,
- version: selectedVer.name,
- hash: selectedVer.hash,
- install_type: installType,
- });
+ await installPlugin(artifactUrl, plugin, selectedVer.name, selectedVer.hash, installType);
}
export async function requestMultiplePluginInstalls(requests: PluginInstallRequest[]) {
- await window.DeckyPluginLoader.callServerMethod('install_plugins', {
- requests: requests.map(({ plugin, installType, selectedVer }) => ({
+ await installPlugins(
+ requests.map(({ plugin, installType, selectedVer }) => ({
name: plugin,
artifact: selectedVer.artifact ?? pluginUrl(selectedVer.hash),
version: selectedVer.name,
hash: selectedVer.hash,
install_type: installType,
})),
- });
+ );
}
export async function checkForUpdates(plugins: Plugin[]): Promise<PluginUpdateMapping> {
diff --git a/frontend/src/utils/settings.ts b/frontend/src/utils/settings.ts
index d390d7ba..cba4887f 100644
--- a/frontend/src/utils/settings.ts
+++ b/frontend/src/utils/settings.ts
@@ -1,8 +1,8 @@
export async function getSetting<T>(key: string, def: T): Promise<T> {
- const res = await window.DeckyPluginLoader.ws.call<[string, T], T>('utilities/settings/get', key, def);
+ const res = await window.DeckyBackend.call<[string, T], T>('utilities/settings/get', key, def);
return res;
}
export async function setSetting<T>(key: string, value: T): Promise<void> {
- await window.DeckyPluginLoader.ws.call<[string, T], void>('utilities/settings/set', key, value);
+ await window.DeckyBackend.call<[string, T], void>('utilities/settings/set', key, value);
}
diff --git a/frontend/src/wsrouter.ts b/frontend/src/wsrouter.ts
index 3a36b5b0..e50b06a7 100644
--- a/frontend/src/wsrouter.ts
+++ b/frontend/src/wsrouter.ts
@@ -1,5 +1,11 @@
import Logger from './logger';
+declare global {
+ interface Window {
+ DeckyBackend: WSRouter;
+ }
+}
+
enum MessageType {
// Call-reply
CALL,
@@ -94,7 +100,6 @@ export class WSRouter extends Logger {
}
async onMessage(msg: MessageEvent) {
- this.debug('WS Message', msg);
try {
const data = JSON.parse(msg.data) as Message;
switch (data.type) {
@@ -108,7 +113,7 @@ export class WSRouter extends Logger {
await this.write({ type: MessageType.ERROR, id: data.id, error: (e as Error)?.stack || e });
}
} else {
- await this.write({ type: MessageType.ERROR, id: data.id, error: 'Route does not exist.' });
+ await this.write({ type: MessageType.ERROR, id: data.id, error: `Route ${data.route} does not exist.` });
}
break;
@@ -152,6 +157,10 @@ export class WSRouter extends Logger {
return resolver.promise;
}
+ callable<Args extends any[] = any[], Return = void>(route: string): (...args: Args) => Promise<Return> {
+ return (...args) => this.call<Args, Return>(route, ...args);
+ }
+
async onError(error: any) {
this.error('WS DISCONNECTED', error);
await this.connect();