feat: pactl-based module loading, more device types
Backend:
- Switched null-sink and loopback to use pactl (works on all systems)
- Generic POST /api/load-module {module, args} for any pactl module
- Fixed SVG toolbar click issue (z-index layering)
Frontend:
- + Add Device dropdown now includes:
- Null Sink (virtual audio output)
- Loopback Device (paired input+output)
- TCP Network Server (module-native-protocol-tcp)
- TCP Tunnel Sink
- TCP Tunnel Source
- Dropdown header and section separators
- Fixed canvas z-index so toolbar is clickable
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
setAutoPin, setAutoDisconnect,
|
||||
saveProfile, loadProfile, deleteProfile,
|
||||
setNodeVolume, setNodeMute,
|
||||
createNullSink, createLoopback,
|
||||
createNullSink, createLoopback, loadModule,
|
||||
} from '../lib/stores';
|
||||
import type { Node, Port, Link } from '../lib/types';
|
||||
|
||||
@@ -423,8 +423,13 @@
|
||||
<!-- Virtual device dropdown -->
|
||||
{#if showVirtualMenu}
|
||||
<div class="virt-menu">
|
||||
<div class="virt-header">Add Virtual Device</div>
|
||||
<button onclick={async () => { showVirtualMenu = false; await createNullSink('pwweb-null-' + Date.now().toString(36)); }}>Null Sink (Virtual Output)</button>
|
||||
<button onclick={async () => { showVirtualMenu = false; await createLoopback('pwweb-loop-' + Date.now().toString(36)); }}>Loopback Device</button>
|
||||
<div class="virt-sep"></div>
|
||||
<button onclick={async () => { showVirtualMenu = false; await loadModule('module-native-protocol-tcp', 'auth-anonymous=1 port=4713'); }}>TCP Network Server</button>
|
||||
<button onclick={async () => { showVirtualMenu = false; await loadModule('module-tunnel-sink', 'server=tcp:127.0.0.1:4713'); }}>TCP Tunnel Sink</button>
|
||||
<button onclick={async () => { showVirtualMenu = false; await loadModule('module-tunnel-source', 'server=tcp:127.0.0.1:4713'); }}>TCP Tunnel Source</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -693,7 +698,7 @@
|
||||
|
||||
<style>
|
||||
.wrap { width: 100%; height: 100vh; background: #14141e; position: relative; overflow: hidden; }
|
||||
.canvas { width: 100%; height: 100%; display: block; cursor: default; }
|
||||
.canvas { width: 100%; height: 100%; display: block; cursor: default; position: absolute; top: 0; left: 0; z-index: 1; }
|
||||
|
||||
.toolbar {
|
||||
position: absolute; top: 0; left: 0; right: 0; z-index: 10;
|
||||
@@ -822,6 +827,11 @@
|
||||
background: #2a2a3e; border: 1px solid #555;
|
||||
border-radius: 4px; box-shadow: 0 4px 12px rgba(0,0,0,0.6);
|
||||
}
|
||||
.virt-header {
|
||||
padding: 4px 16px; font-size: 9px; color: #666; text-transform: uppercase;
|
||||
letter-spacing: 1px; border-bottom: 1px solid #444;
|
||||
}
|
||||
.virt-sep { height: 1px; background: #444; margin: 2px 0; }
|
||||
.virt-menu button {
|
||||
display: block; width: 100%; padding: 6px 16px;
|
||||
background: none; border: none; color: #ccc;
|
||||
|
||||
@@ -392,7 +392,7 @@ export async function createNullSink(name: string): Promise<number | null> {
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.module_id || null;
|
||||
return data.module_id > 0 ? data.module_id : null;
|
||||
} catch (e) {
|
||||
console.error('[api] create-null-sink failed:', e);
|
||||
return null;
|
||||
@@ -407,13 +407,28 @@ export async function createLoopback(name: string): Promise<number | null> {
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.module_id || null;
|
||||
return data.module_id > 0 ? data.module_id : null;
|
||||
} catch (e) {
|
||||
console.error('[api] create-loopback failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadModule(module: string, args: string): Promise<number | null> {
|
||||
try {
|
||||
const res = await fetch('/api/load-module', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ module, args }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.module_id > 0 ? data.module_id : null;
|
||||
} catch (e) {
|
||||
console.error('[api] load-module failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function unloadModule(moduleId: number) {
|
||||
try {
|
||||
await fetch('/api/unload-module', {
|
||||
|
||||
Reference in New Issue
Block a user