From 15dddf7ec08f59b4bc607673807d3e68ee96f0a5 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 7 Aug 2026 12:00:13 +0200 Subject: [PATCH] feat(web): the Dashboard shows the audio wiring verdict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Audio wiring card (Windows hosts) below the status tiles: a readiness badge (Ready / No microphone / No game audio / Not wired), the friendly names carrying each role, and the degradation notes that were previously visible only in the host log — mic withheld for game audio, the known- degraded last resort, a narrowing endpoint. api/openapi.json regenerated from the host build (AudioWiring + RuntimeStatus.audio); en+de messages. --- api/openapi.json | 56 ++++++++++++++++++++++++++++ web/messages/de.json | 10 +++++ web/messages/en.json | 10 +++++ web/src/sections/Dashboard/view.tsx | 58 +++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/api/openapi.json b/api/openapi.json index b62c484c..d463c6cb 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -4045,6 +4045,51 @@ } } }, + "AudioWiring": { + "type": "object", + "description": "The Windows host's audio wiring verdict — which endpoint carries each role. The names are\nthe endpoints' friendly names as the Sound settings show them (on current hosts the minted\n\"Punktfunk\" instances of Steam's streaming drivers).", + "required": [ + "readiness", + "mic_withheld", + "last_resort" + ], + "properties": { + "last_resort": { + "type": "boolean", + "description": "The loopback is the known-degraded last resort — desktop audio may be silent until the\nendpoint set changes." + }, + "loopback": { + "type": [ + "string", + "null" + ], + "description": "Friendly name of the desktop-audio loopback source; absent = desktop audio unavailable." + }, + "mic": { + "type": [ + "string", + "null" + ], + "description": "Friendly name of the virtual-mic write target; absent = mic passthrough unavailable." + }, + "mic_withheld": { + "type": "boolean", + "description": "The mic was WITHHELD so game audio could keep the only working sink — mic passthrough\nneeds Steam installed (the host mints its own microphone) or a virtual cable." + }, + "narrowing": { + "type": [ + "string", + "null" + ], + "description": "Why the chosen loopback endpoint NARROWS the desktop mix (rate/channels), when it does." + }, + "readiness": { + "type": "string", + "description": "`full` | `audio_only` | `mic_only` | `none` — whether desktop audio and mic passthrough\neach have an endpoint at all.", + "example": "full" + } + } + }, "AvailableCompositor": { "type": "object", "description": "A compositor backend the host can drive a virtual output on, and whether it's usable now.", @@ -6805,6 +6850,17 @@ "description": "Number of live streaming sessions across BOTH planes (GameStream + native punktfunk/1). The\nnative server admits concurrent sessions, so this can exceed 1; `session`/`stream` below\ndescribe a single representative session for the detail card.", "minimum": 0 }, + "audio": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AudioWiring", + "description": "The audio wiring verdict (Windows hosts; absent on other platforms and before the first\nwiring pass). Present even while idle — the wiring exists for the host's lifetime." + } + ] + }, "audio_streaming": { "type": "boolean", "description": "True while the audio stream thread is running." diff --git a/web/messages/de.json b/web/messages/de.json index 6e29a9e8..56f2cb49 100644 --- a/web/messages/de.json +++ b/web/messages/de.json @@ -73,6 +73,16 @@ "status_paired_count": "Gekoppelte Geräte", "status_pin_waiting": "Wartet", "status_pin_none": "Keine", + "audio_wiring_title": "Audio-Verkabelung", + "audio_output": "Spielaudio", + "audio_microphone": "Mikrofon", + "audio_unavailable": "Nicht verfügbar", + "audio_ready": "Bereit", + "audio_ready_no_mic": "Kein Mikrofon", + "audio_no_output": "Kein Spielaudio", + "audio_none": "Nicht verkabelt", + "audio_mic_withheld": "Der Mikrofon-Endpunkt überträgt gerade das Spielaudio — installiere Steam, damit der Host sein eigenes Mikrofon anlegen kann (Steam muss nie laufen).", + "audio_last_resort": "Spielaudio läuft über einen eingeschränkten Ersatz-Endpunkt und kann stumm bleiben, bis ein Ausgabegerät erscheint.", "status_pin_pending": "Kopplungs-PIN ausstehend", "stream_codec": "Codec", "stream_resolution": "Auflösung", diff --git a/web/messages/en.json b/web/messages/en.json index 3e5f70f2..da6286ae 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -73,6 +73,16 @@ "status_paired_count": "Paired clients", "status_pin_waiting": "Waiting", "status_pin_none": "None", + "audio_wiring_title": "Audio wiring", + "audio_output": "Game audio", + "audio_microphone": "Microphone", + "audio_unavailable": "Unavailable", + "audio_ready": "Ready", + "audio_ready_no_mic": "No microphone", + "audio_no_output": "No game audio", + "audio_none": "Not wired", + "audio_mic_withheld": "The microphone endpoint is carrying game audio — install Steam so the host can mint its own microphone (Steam never has to run).", + "audio_last_resort": "Game audio is on a degraded fallback endpoint and may be silent until an output device appears.", "status_pin_pending": "Pairing PIN pending", "stream_codec": "Codec", "stream_resolution": "Resolution", diff --git a/web/src/sections/Dashboard/view.tsx b/web/src/sections/Dashboard/view.tsx index 81d3dd9b..792b3994 100644 --- a/web/src/sections/Dashboard/view.tsx +++ b/web/src/sections/Dashboard/view.tsx @@ -2,6 +2,7 @@ import Section from "@unom/ui/section"; import { MonitorPlay, RefreshCw, Video, Volume2, ZapOff } from "lucide-react"; import type { FC, ReactNode } from "react"; import type { ActiveGame } from "@/api/gen/model/activeGame"; +import type { AudioWiring } from "@/api/gen/model/audioWiring"; import type { GameEntry } from "@/api/gen/model/gameEntry"; import type { RuntimeStatus } from "@/api/gen/model/runtimeStatus"; import { QueryState } from "@/components/query-state"; @@ -87,6 +88,12 @@ export const DashboardView: FC<{ + {/* The wiring verdict (Windows hosts): WHICH endpoints carry game + audio and the microphone, and the degradations that used to be + visible only in the host log — a silent host looks identical to a + quiet game without this. */} + {s.audio && } + {/* Above the session card: a game the host is about to close is the most time-sensitive thing on this page. */} = ({ audio }) => { + const badge: { variant: "success" | "secondary" | "destructive"; text: string } = + audio.readiness === "full" + ? { variant: "success", text: m.audio_ready() } + : audio.readiness === "audio_only" + ? { variant: "secondary", text: m.audio_ready_no_mic() } + : audio.readiness === "mic_only" + ? { variant: "destructive", text: m.audio_no_output() } + : { variant: "destructive", text: m.audio_none() }; + const notes = [ + audio.mic_withheld ? m.audio_mic_withheld() : undefined, + audio.last_resort ? m.audio_last_resort() : undefined, + audio.narrowing, + ].filter((n): n is string => !!n); + return ( + + + + + {m.audio_wiring_title()} + + {badge.text} + + +
+ + +
+ {notes.length > 0 && ( +
    + {notes.map((n) => ( +
  • {n}
  • + ))} +
+ )} +
+
+ ); +}; + const StatCard: FC<{ icon: ReactNode; label: string; on: boolean }> = ({ icon, label,