mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
feat: use alternative ports if default ports are already in use
This commit is contained in:
13 files changed
+71
-25
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"identifier": "corsfetch",
|
"identifier": "corsfetch",
|
||||||
"local": true,
|
"local": true,
|
||||||
"remote": { "urls": ["http://localhost:57118"] },
|
"remote": { "urls": ["http://localhost:*"] },
|
||||||
"windows": ["*"],
|
"windows": ["*"],
|
||||||
"permissions": ["cors-fetch:default"]
|
"permissions": ["cors-fetch:default"]
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,11 @@ pub async fn update_devices() {
|
|||||||
let _ = app.get_webview_window("main").unwrap().emit("devices", DEVICES.clone());
|
let _ = app.get_webview_window("main").unwrap().emit("devices", DEVICES.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub async fn get_port_base() -> u16 {
|
||||||
|
*crate::plugins::PORT_BASE
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn get_categories() -> HashMap<String, Category> {
|
pub async fn get_categories() -> HashMap<String, Category> {
|
||||||
CATEGORIES.read().await.clone()
|
CATEGORIES.read().await.clone()
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ async fn main() {
|
|||||||
|
|
||||||
let app = match Builder::default()
|
let app = match Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
frontend::get_devices,
|
|
||||||
frontend::restart,
|
frontend::restart,
|
||||||
|
frontend::get_devices,
|
||||||
|
frontend::get_port_base,
|
||||||
frontend::get_categories,
|
frontend::get_categories,
|
||||||
frontend::get_localisations,
|
frontend::get_localisations,
|
||||||
frontend::get_applications,
|
frontend::get_applications,
|
||||||
|
|||||||
@@ -31,6 +31,20 @@ enum PluginInstance {
|
|||||||
pub static DEVICE_NAMESPACES: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
pub static DEVICE_NAMESPACES: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
||||||
static INSTANCES: Lazy<Mutex<HashMap<String, PluginInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
static INSTANCES: Lazy<Mutex<HashMap<String, PluginInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
pub static PORT_BASE: Lazy<u16> = Lazy::new(|| {
|
||||||
|
let mut base = 57116;
|
||||||
|
loop {
|
||||||
|
let websocket_result = std::net::TcpListener::bind(format!("0.0.0.0:{}", base));
|
||||||
|
let webserver_result = std::net::TcpListener::bind(format!("0.0.0.0:{}", base + 2));
|
||||||
|
if websocket_result.is_ok() && webserver_result.is_ok() {
|
||||||
|
log::debug!("Using ports {} and {}", base, base + 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
base += 1;
|
||||||
|
}
|
||||||
|
base
|
||||||
|
});
|
||||||
|
|
||||||
/// Initialise a plugin from a given directory.
|
/// Initialise a plugin from a given directory.
|
||||||
pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> {
|
pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> {
|
||||||
let plugin_uuid = path.file_name().unwrap().to_str().unwrap();
|
let plugin_uuid = path.file_name().unwrap().to_str().unwrap();
|
||||||
@@ -152,10 +166,11 @@ pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let code_path = code_path.unwrap();
|
let code_path = code_path.unwrap();
|
||||||
let args = ["-port", "57116", "-pluginUUID", plugin_uuid, "-registerEvent", "registerPlugin", "-info"];
|
let port_string = PORT_BASE.to_string();
|
||||||
|
let args = ["-port", port_string.as_str(), "-pluginUUID", plugin_uuid, "-registerEvent", "registerPlugin", "-info"];
|
||||||
|
|
||||||
if code_path.to_lowercase().ends_with(".html") || code_path.to_lowercase().ends_with(".htm") || code_path.to_lowercase().ends_with(".xhtml") {
|
if code_path.to_lowercase().ends_with(".html") || code_path.to_lowercase().ends_with(".htm") || code_path.to_lowercase().ends_with(".xhtml") {
|
||||||
let url = "http://localhost:57118/".to_owned() + path.join(code_path).to_str().unwrap();
|
let url = format!("http://localhost:{}/", *PORT_BASE + 2) + path.join(code_path).to_str().unwrap();
|
||||||
let window = tauri::WebviewWindowBuilder::new(APP_HANDLE.get().unwrap(), plugin_uuid.replace('.', "_"), tauri::WebviewUrl::External(url.parse()?))
|
let window = tauri::WebviewWindowBuilder::new(APP_HANDLE.get().unwrap(), plugin_uuid.replace('.', "_"), tauri::WebviewUrl::External(url.parse()?))
|
||||||
.title(plugin_uuid)
|
.title(plugin_uuid)
|
||||||
.visible(false)
|
.visible(false)
|
||||||
@@ -180,7 +195,7 @@ pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> {
|
|||||||
}};
|
}};
|
||||||
opendeckInit();
|
opendeckInit();
|
||||||
"#,
|
"#,
|
||||||
port = 57116,
|
port = *PORT_BASE,
|
||||||
uuid = plugin_uuid,
|
uuid = plugin_uuid,
|
||||||
event = "registerPlugin",
|
event = "registerPlugin",
|
||||||
info = serde_json::to_string(&info)?
|
info = serde_json::to_string(&info)?
|
||||||
@@ -431,7 +446,7 @@ pub fn initialise_plugins() {
|
|||||||
|
|
||||||
/// Start the WebSocket server that plugins communicate with.
|
/// Start the WebSocket server that plugins communicate with.
|
||||||
async fn init_websocket_server() {
|
async fn init_websocket_server() {
|
||||||
let listener = match TcpListener::bind("0.0.0.0:57116").await {
|
let listener = match TcpListener::bind(format!("0.0.0.0:{}", *PORT_BASE)).await {
|
||||||
Ok(listener) => listener,
|
Ok(listener) => listener,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Failed to bind plugin WebSocket server to socket: {}", error);
|
error!("Failed to bind plugin WebSocket server to socket: {}", error);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fn mime(extension: &str) -> String {
|
|||||||
/// Start a simple webserver to serve files of plugins that run in a browser environment.
|
/// Start a simple webserver to serve files of plugins that run in a browser environment.
|
||||||
pub async fn init_webserver(prefix: PathBuf) {
|
pub async fn init_webserver(prefix: PathBuf) {
|
||||||
let server = {
|
let server = {
|
||||||
let listener = std::net::TcpListener::bind("0.0.0.0:57118").unwrap();
|
let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", *super::PORT_BASE + 2)).unwrap();
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data:; frame-src http://localhost:57118; script-src 'self' 'unsafe-inline'; connect-src ipc: http://ipc.localhost https://openactionapi.github.io https://api.github.com https://raw.githubusercontent.com https://plugins.amankhanna.me;"
|
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data:; frame-src http://localhost:*; script-src 'self' 'unsafe-inline'; connect-src ipc: http://ipc.localhost https://openactionapi.github.io https://api.github.com https://raw.githubusercontent.com https://plugins.amankhanna.me;"
|
||||||
},
|
},
|
||||||
"withGlobalTauri": true
|
"withGlobalTauri": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass";
|
import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass";
|
||||||
|
|
||||||
|
import { getWebserverUrl } from "$lib/ports";
|
||||||
import { localisations } from "$lib/settings";
|
import { localisations } from "$lib/settings";
|
||||||
import { PRODUCT_NAME } from "$lib/singletons";
|
import { PRODUCT_NAME } from "$lib/singletons";
|
||||||
|
|
||||||
@@ -49,9 +50,7 @@
|
|||||||
<summary class="text-xl font-semibold dark:text-neutral-300">
|
<summary class="text-xl font-semibold dark:text-neutral-300">
|
||||||
{#if icon || (actions[0] && plugins.find((x) => x.id == actions[0].plugin) && categories[name].actions.every((x) => x.plugin == actions[0].plugin))}
|
{#if icon || (actions[0] && plugins.find((x) => x.id == actions[0].plugin) && categories[name].actions.every((x) => x.plugin == actions[0].plugin))}
|
||||||
<img
|
<img
|
||||||
src={icon
|
src={icon ? (!icon.startsWith("opendeck/") ? getWebserverUrl(icon) : icon.replace("opendeck", "")) : getWebserverUrl(plugins.find((x) => x.id == actions[0].plugin).icon)}
|
||||||
? (!icon.startsWith("opendeck/") ? "http://localhost:57118/" + icon : icon.replace("opendeck", ""))
|
|
||||||
: "http://localhost:57118/" + plugins.find((x) => x.id == actions[0].plugin).icon}
|
|
||||||
alt={name}
|
alt={name}
|
||||||
class="w-5 h-5 rounded-xs ml-1 -mt-1 inline"
|
class="w-5 h-5 rounded-xs ml-1 -mt-1 inline"
|
||||||
/>
|
/>
|
||||||
@@ -67,7 +66,7 @@
|
|||||||
on:dragstart={(event) => event.dataTransfer?.setData("action", JSON.stringify(action))}
|
on:dragstart={(event) => event.dataTransfer?.setData("action", JSON.stringify(action))}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={!action.icon.startsWith("opendeck/") ? "http://localhost:57118/" + action.icon : action.icon.replace("opendeck", "")}
|
src={!action.icon.startsWith("opendeck/") ? getWebserverUrl(action.icon) : action.icon.replace("opendeck", "")}
|
||||||
alt={$localisations?.[action.plugin]?.[action.uuid]?.Tooltip ?? action.tooltip}
|
alt={$localisations?.[action.plugin]?.[action.uuid]?.Tooltip ?? action.tooltip}
|
||||||
class="w-12 h-12 rounded-xs pointer-events-none"
|
class="w-12 h-12 rounded-xs pointer-events-none"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,14 +4,15 @@
|
|||||||
import CloudArrowDown from "phosphor-svelte/lib/CloudArrowDown";
|
import CloudArrowDown from "phosphor-svelte/lib/CloudArrowDown";
|
||||||
import FileArrowUp from "phosphor-svelte/lib/FileArrowUp";
|
import FileArrowUp from "phosphor-svelte/lib/FileArrowUp";
|
||||||
import Gear from "phosphor-svelte/lib/Gear";
|
import Gear from "phosphor-svelte/lib/Gear";
|
||||||
import ListedPlugin from "./ListedPlugin.svelte";
|
|
||||||
import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass";
|
import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass";
|
||||||
|
import Trash from "phosphor-svelte/lib/Trash";
|
||||||
|
import WarningCircle from "phosphor-svelte/lib/WarningCircle";
|
||||||
|
import ListedPlugin from "./ListedPlugin.svelte";
|
||||||
import PluginDetails from "./PluginDetails.svelte";
|
import PluginDetails from "./PluginDetails.svelte";
|
||||||
import Popup from "./Popup.svelte";
|
import Popup from "./Popup.svelte";
|
||||||
import Tooltip from "./Tooltip.svelte";
|
import Tooltip from "./Tooltip.svelte";
|
||||||
import Trash from "phosphor-svelte/lib/Trash";
|
|
||||||
import WarningCircle from "phosphor-svelte/lib/WarningCircle";
|
|
||||||
|
|
||||||
|
import { getWebserverUrl } from "$lib/ports";
|
||||||
import { localisations, settings } from "$lib/settings";
|
import { localisations, settings } from "$lib/settings";
|
||||||
import { actionList, deviceSelector } from "$lib/singletons";
|
import { actionList, deviceSelector } from "$lib/singletons";
|
||||||
|
|
||||||
@@ -168,7 +169,7 @@
|
|||||||
<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
{#each installed.sort((a, b) => (a.builtin && !b.builtin) ? -1 : (b.builtin && !a.builtin) ? 1 : a.id.localeCompare(b.id)) as plugin}
|
{#each installed.sort((a, b) => (a.builtin && !b.builtin) ? -1 : (b.builtin && !a.builtin) ? 1 : a.id.localeCompare(b.id)) as plugin}
|
||||||
<ListedPlugin
|
<ListedPlugin
|
||||||
icon="http://localhost:57118/{plugin.icon}"
|
icon={getWebserverUrl(plugin.icon)}
|
||||||
name={($localisations && $localisations[plugin.id] && $localisations[plugin.id].Name) ? $localisations[plugin.id].Name : plugin.name}
|
name={($localisations && $localisations[plugin.id] && $localisations[plugin.id].Name) ? $localisations[plugin.id].Name : plugin.name}
|
||||||
subtitle={plugin.version}
|
subtitle={plugin.version}
|
||||||
disconnected={!plugin.registered}
|
disconnected={!plugin.registered}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||||
import type { Profile } from "$lib/Profile";
|
import type { Profile } from "$lib/Profile";
|
||||||
|
|
||||||
|
import { getWebserverUrl, getWebSocketPort } from "$lib/ports";
|
||||||
import { inspectedInstance } from "$lib/propertyInspector";
|
import { inspectedInstance } from "$lib/propertyInspector";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
@@ -27,13 +28,13 @@
|
|||||||
coordinates = { row: Math.floor(position / device.columns), column: position % device.columns };
|
coordinates = { row: Math.floor(position / device.columns), column: position % device.columns };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance == null || !iframe.src || !iframe.src.startsWith("http://localhost:57118")) return;
|
if (instance == null || !iframe.src || !iframe.src.startsWith(getWebserverUrl())) return;
|
||||||
const info = JSON.stringify(await invoke("make_info", { plugin: instance.action.plugin }));
|
const info = JSON.stringify(await invoke("make_info", { plugin: instance.action.plugin }));
|
||||||
|
|
||||||
iframe?.contentWindow?.postMessage({
|
iframe?.contentWindow?.postMessage({
|
||||||
event: "connect",
|
event: "connect",
|
||||||
payload: [
|
payload: [
|
||||||
57116,
|
getWebSocketPort(),
|
||||||
instance.context,
|
instance.context,
|
||||||
"registerPropertyInspector",
|
"registerPropertyInspector",
|
||||||
info,
|
info,
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}, "http://localhost:57118");
|
}, getWebserverUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
const closePopup = (context: string) => {
|
const closePopup = (context: string) => {
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
iframe.style.width = "100%";
|
iframe.style.width = "100%";
|
||||||
iframe.style.height = "100%";
|
iframe.style.height = "100%";
|
||||||
iframe.style.display = $inspectedInstance == context ? "block" : "none";
|
iframe.style.display = $inspectedInstance == context ? "block" : "none";
|
||||||
iframe.contentWindow?.postMessage({ event: "windowClosed" }, "http://localhost:57118");
|
iframe.contentWindow?.postMessage({ event: "windowClosed" }, getWebserverUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
iframePopupsOpen = iframePopupsOpen.filter((e) => e != context);
|
iframePopupsOpen = iframePopupsOpen.filter((e) => e != context);
|
||||||
@@ -140,9 +141,9 @@
|
|||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "http://localhost:57118");
|
}, getWebserverUrl());
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
iframes[data.payload.context]?.contentWindow?.postMessage({ event: "fetchError", payload: { id: data.payload.id, error } }, "http://localhost:57118");
|
iframes[data.payload.context]?.contentWindow?.postMessage({ event: "fetchError", payload: { id: data.payload.id, error } }, getWebserverUrl());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -176,7 +177,7 @@
|
|||||||
title="Property inspector"
|
title="Property inspector"
|
||||||
class="w-full h-full hidden"
|
class="w-full h-full hidden"
|
||||||
class:block!={$inspectedInstance == instance.context}
|
class:block!={$inspectedInstance == instance.context}
|
||||||
src={"http://localhost:57118/" + instance.action.property_inspector + "|opendeck_property_inspector"}
|
src={getWebserverUrl(instance.action.property_inspector + "|opendeck_property_inspector")}
|
||||||
name={instance.context}
|
name={instance.context}
|
||||||
bind:this={iframes[instance.context]}
|
bind:this={iframes[instance.context]}
|
||||||
on:load={() => iframeOnLoad(instance)}
|
on:load={() => iframeOnLoad(instance)}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
let portBase = 57116;
|
||||||
|
let initialised = false;
|
||||||
|
|
||||||
|
export async function initPortBase(): Promise<void> {
|
||||||
|
if (initialised) return;
|
||||||
|
portBase = await invoke<number>("get_port_base");
|
||||||
|
initialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebSocketPort(): number {
|
||||||
|
return portBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebserverUrl(path: string = ""): string {
|
||||||
|
return `http://localhost:${portBase + 2}/${path}`;
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import type { ActionState } from "./ActionState.ts";
|
import type { ActionState } from "./ActionState.ts";
|
||||||
import type { Context } from "./Context.ts";
|
import type { Context } from "./Context.ts";
|
||||||
|
|
||||||
|
import { getWebserverUrl } from "./ports.ts";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
export function getImage(image: string | undefined, fallback: string | undefined): string {
|
export function getImage(image: string | undefined, fallback: string | undefined): string {
|
||||||
if (!image) return fallback ? getImage(fallback, undefined) : "/alert.png";
|
if (!image) return fallback ? getImage(fallback, undefined) : "/alert.png";
|
||||||
if (image.startsWith("opendeck/")) return image.replace("opendeck", "");
|
if (image.startsWith("opendeck/")) return image.replace("opendeck", "");
|
||||||
if (!image.startsWith("data:")) return "http://localhost:57118/" + image;
|
if (!image.startsWith("data:")) return getWebserverUrl(image);
|
||||||
const svgxmlre = /^data:image\/svg\+xml(?!.*?;base64.*?)(?:;[\w=]*)*,(.+)/;
|
const svgxmlre = /^data:image\/svg\+xml(?!.*?;base64.*?)(?:;[\w=]*)*,(.+)/;
|
||||||
const base64re = /^data:image\/(apng|avif|gif|jpeg|png|svg\+xml|webp|bmp|x-icon|tiff);base64,([A-Za-z0-9+/]+={0,2})?/;
|
const base64re = /^data:image\/(apng|avif|gif|jpeg|png|svg\+xml|webp|bmp|x-icon|tiff);base64,([A-Za-z0-9+/]+={0,2})?/;
|
||||||
if (svgxmlre.test(image)) {
|
if (svgxmlre.test(image)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type { DeviceInfo } from "$lib/DeviceInfo";
|
import type { DeviceInfo } from "$lib/DeviceInfo";
|
||||||
import type { Profile } from "$lib/Profile";
|
import type { Profile } from "$lib/Profile";
|
||||||
|
|
||||||
|
import { initPortBase } from "$lib/ports";
|
||||||
import { inspectedParentAction } from "$lib/propertyInspector";
|
import { inspectedParentAction } from "$lib/propertyInspector";
|
||||||
import { actionList, deviceSelector, profileManager } from "$lib/singletons";
|
import { actionList, deviceSelector, profileManager } from "$lib/singletons";
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@
|
|||||||
let devices: { [id: string]: DeviceInfo } = {};
|
let devices: { [id: string]: DeviceInfo } = {};
|
||||||
let selectedDevice: string;
|
let selectedDevice: string;
|
||||||
let selectedProfiles: { [id: string]: Profile } = {};
|
let selectedProfiles: { [id: string]: Profile } = {};
|
||||||
|
|
||||||
|
initPortBase();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:dragover={(event) => event.preventDefault()} on:drop={(event) => event.preventDefault()} />
|
<svelte:window on:dragover={(event) => event.preventDefault()} on:drop={(event) => event.preventDefault()} />
|
||||||
|
|||||||
+2
-1
@@ -9,6 +9,7 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true
|
"strict": true,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user