|
7 | 7 | #include <QStandardPaths> |
8 | 8 | #include <QFileInfo> |
9 | 9 |
|
| 10 | +#include "../core/constants.h" |
10 | 11 | #include "../core/logging.h" |
11 | 12 |
|
12 | 13 | namespace PlasmaZones { |
13 | 14 |
|
14 | 15 | static constexpr int kAsciiMaxRange = 1000; // CAVA ascii_max_range |
15 | | -static constexpr int kMinBars = 16; |
16 | | -static constexpr int kMaxBars = 256; |
| 16 | +static_assert(Audio::MinBars % 2 == 0, "Audio::MinBars must be even for CAVA stereo"); |
| 17 | +static_assert(Audio::MaxBars % 2 == 0, "Audio::MaxBars must be even for CAVA stereo"); |
17 | 18 |
|
18 | 19 | CavaService::CavaService(QObject* parent) |
19 | 20 | : QObject(parent) |
@@ -52,15 +53,18 @@ void CavaService::start() |
52 | 53 | this, &CavaService::onReadyReadStandardOutput); |
53 | 54 | connect(m_process, &QProcess::stateChanged, |
54 | 55 | this, &CavaService::onProcessStateChanged); |
| 56 | + connect(m_process, &QProcess::finished, |
| 57 | + this, &CavaService::onProcessFinished); |
55 | 58 | connect(m_process, &QProcess::errorOccurred, |
56 | 59 | this, &CavaService::onProcessError); |
57 | 60 | } |
58 | 61 |
|
59 | 62 | m_stdoutBuffer.clear(); |
60 | 63 | m_spectrum.clear(); |
61 | 64 |
|
62 | | - // Kurve-style: pass config via stdin, read raw output from stdout |
63 | | - m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); |
| 65 | + // Kurve-style: pass config via stdin, read raw output from stdout. |
| 66 | + // Use SeparateChannels so we can capture stderr for error diagnostics. |
| 67 | + m_process->setProcessChannelMode(QProcess::SeparateChannels); |
64 | 68 | m_process->start(QStringLiteral("sh"), QStringList{QStringLiteral("-c"), |
65 | 69 | QStringLiteral("exec %1 -p /dev/stdin <<'CAVAEOF'\n%2\nCAVAEOF").arg(cavaPath, m_config)}); |
66 | 70 |
|
@@ -90,7 +94,10 @@ bool CavaService::isRunning() const |
90 | 94 |
|
91 | 95 | void CavaService::setBarCount(int count) |
92 | 96 | { |
93 | | - const int clamped = qBound(kMinBars, count, kMaxBars); |
| 97 | + // CAVA requires even bar count for stereo output (bars split between L/R channels). |
| 98 | + // Round to even first, then clamp — ensures we never exceed MaxBars after rounding. |
| 99 | + int even = (count % 2 != 0) ? count + 1 : count; |
| 100 | + const int clamped = qBound(Audio::MinBars, even, Audio::MaxBars); |
94 | 101 | if (m_barCount != clamped) { |
95 | 102 | m_barCount = clamped; |
96 | 103 | if (isRunning()) { |
@@ -207,6 +214,19 @@ void CavaService::onProcessStateChanged(QProcess::ProcessState state) |
207 | 214 | } |
208 | 215 | } |
209 | 216 |
|
| 217 | +void CavaService::onProcessFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/) |
| 218 | +{ |
| 219 | + if (m_stopping || m_pendingRestart) { |
| 220 | + return; |
| 221 | + } |
| 222 | + if (exitCode != 0) { |
| 223 | + const QByteArray stderrOutput = m_process ? m_process->readAllStandardError().left(500) |
| 224 | + : QByteArray(); |
| 225 | + qCWarning(lcOverlay) << "CAVA exited with code" << exitCode |
| 226 | + << "stderr:" << stderrOutput; |
| 227 | + } |
| 228 | +} |
| 229 | + |
210 | 230 | void CavaService::onProcessError(QProcess::ProcessError error) |
211 | 231 | { |
212 | 232 | // Suppress errors from intentional stop() or restartAsync() — SIGTERM causes QProcess::Crashed |
|
0 commit comments