Tier 0: render what the services already decided, honestly
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -34,6 +34,17 @@ Singleton {
|
||||
// chip in the channel strip.
|
||||
property string playingChannel: ""
|
||||
|
||||
// Why the last test did not work, "" when it did.
|
||||
//
|
||||
// Every failure here is otherwise invisible, and invisible in the worst
|
||||
// possible way: this page exists to answer "is this the right speaker" and
|
||||
// "does this microphone work", and both questions are answered by silence.
|
||||
// A pw-play that is not installed, a sample file that is missing, a capture
|
||||
// device that never produces a sample -- all three look exactly like a dead
|
||||
// speaker or a dead microphone from the chair, which is the wrong answer
|
||||
// to the question the page was opened to ask.
|
||||
property string lastError: ""
|
||||
|
||||
// ── Channels ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// The freedesktop theme ships one spoken sample per channel, and the set it
|
||||
@@ -86,15 +97,22 @@ Singleton {
|
||||
|
||||
function playChannel(node: var, channelName: string): void {
|
||||
const file = root.sampleFor(String(channelName ?? ""));
|
||||
if (file === "")
|
||||
if (file === "") {
|
||||
root.lastError = "No sample is installed for that channel.";
|
||||
return;
|
||||
}
|
||||
root.playSample(node, file, String(channelName));
|
||||
}
|
||||
|
||||
function playSample(node: var, file: string, channelName: string): void {
|
||||
const target = String(node?.name ?? "");
|
||||
if (target === "" || player.running)
|
||||
if (player.running)
|
||||
return;
|
||||
const target = String(node?.name ?? "");
|
||||
if (target === "") {
|
||||
root.lastError = "PipeWire has no name for that output, so nothing could be played to it.";
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
root.playingChannel = channelName;
|
||||
player.command = ["pw-play", "--target", target, file];
|
||||
player.running = true;
|
||||
@@ -102,7 +120,14 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: player
|
||||
onExited: (code, status) => root.playingChannel = ""
|
||||
onExited: (code, status) => {
|
||||
root.playingChannel = "";
|
||||
// A silent speaker and a pw-play that never ran are the same
|
||||
// experience and different problems.
|
||||
if (code !== 0)
|
||||
root.lastError = "The test sound could not be played — pw-play exited with code "
|
||||
+ code + ". The freedesktop sound theme or pipewire-utils may be missing.";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Microphone test ─────────────────────────────────────────────────────
|
||||
@@ -124,9 +149,15 @@ Singleton {
|
||||
readonly property int micTestRate: 48000
|
||||
|
||||
function startMicTest(sourceNode: var, sinkNode: var): void {
|
||||
const source = String(sourceNode?.name ?? "");
|
||||
if (source === "" || root.micTestState !== "idle")
|
||||
if (root.micTestState !== "idle")
|
||||
return;
|
||||
const source = String(sourceNode?.name ?? "");
|
||||
if (source === "") {
|
||||
root.lastError = "PipeWire has no name for that microphone, so nothing could be recorded from it.";
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
root.micTestTimedOut = false;
|
||||
|
||||
// pw-play needs a target too, or the playback lands on the default
|
||||
// output rather than the one being looked at.
|
||||
@@ -162,6 +193,10 @@ Singleton {
|
||||
|
||||
property string micTestSink: ""
|
||||
property bool micTestCancelled: false
|
||||
// Set by the watchdog, read by the exit handler: a recorder killed for
|
||||
// stalling and a recorder that failed outright both come back non-zero,
|
||||
// and only one of them means "this microphone produced no sound".
|
||||
property bool micTestTimedOut: false
|
||||
|
||||
Timer {
|
||||
id: micTestWatchdog
|
||||
@@ -169,8 +204,10 @@ Singleton {
|
||||
// capture that has stalled rather than one that is merely slow.
|
||||
interval: (root.micTestSeconds + 3) * 1000
|
||||
onTriggered: {
|
||||
if (recorder.running)
|
||||
if (recorder.running) {
|
||||
root.micTestTimedOut = true;
|
||||
recorder.signal(15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +217,21 @@ Singleton {
|
||||
micTestWatchdog.stop();
|
||||
if (root.micTestCancelled) {
|
||||
root.micTestCancelled = false;
|
||||
root.micTestTimedOut = false;
|
||||
root.micTestState = "idle";
|
||||
return;
|
||||
}
|
||||
if (root.micTestTimedOut) {
|
||||
root.micTestTimedOut = false;
|
||||
root.micTestState = "idle";
|
||||
root.lastError = "That microphone produced no sound in "
|
||||
+ (root.micTestSeconds + 3) + " seconds. It may be muted at the device.";
|
||||
return;
|
||||
}
|
||||
if (code !== 0) {
|
||||
root.micTestState = "idle";
|
||||
root.lastError = "Nothing could be recorded — pw-record exited with code "
|
||||
+ code + ".";
|
||||
return;
|
||||
}
|
||||
root.micTestState = "playing";
|
||||
@@ -198,8 +245,14 @@ Singleton {
|
||||
Process {
|
||||
id: playback
|
||||
onExited: (code, status) => {
|
||||
// A cancelled playback was killed on purpose; its non-zero exit is
|
||||
// not a failure to report.
|
||||
const cancelled = root.micTestCancelled;
|
||||
root.micTestCancelled = false;
|
||||
root.micTestState = "idle";
|
||||
if (!cancelled && code !== 0)
|
||||
root.lastError = "The recording was made but could not be played back — "
|
||||
+ "pw-play exited with code " + code + ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user