refactor(cava): fix thread-safety, resource leaks, and style violations

Comprehensive refactor of the cava module backend and frontends.

Style & naming
- Rename Cava -> CavaRaw; snake_case methods -> lowerCamelCase
- Replace NULL with nullptr; replace C-style casts with static_cast
- Add explicit standard-library includes (<memory>, <string>, <chrono>)
- Fix missing trailing underscores on member variables

Architecture
- Remove Gtk::GLArea multiple inheritance in CavaGLSL (composition)
- Return std::unique_ptr from factory; add doAction() to GLSL variant
- Encapsulate thread timing arithmetic in AdaptiveDelay struct

Thread safety & correctness
- Replace raw sigc::signal with SafeSignal for cross-thread marshalling
- Fix data race between loadConfig() and out_thread_ (recursive_mutex)
- Fix audio_raw shallow-copy use-after-free via deep-copy AudioRaw payload
- Make loadConfig() exception-safe with CavaConfigGuard RAII
- Fix blocking read_thread_ race on shutdown (condition_variable + timeout join)
- Fix isSilent() data race (acquire pthread mutex)
- Eliminate doUpdate() recursion (iteration instead)
- Guard audio_raw_clean() against uninitialized state
- Fix format-icons underflow and cava_config buffer overflow
- Store config by value to prevent dangling references on reload
- Cache frontend config and refresh on runtime changes
- Fix signed-char icon lookup bug on x86
- Prevent Json::Value mutation bloat via const-ref lookups
- Broaden exception catches in worker threads (std::exception)

Resource management & GL robustness
- Fix OpenGL resource leaks (persist VBO/IBO/VAO; explicit destructor cleanup)
- Fix shader error handling crashes (valid infoLog allocation)
- Cache uniform locations instead of querying per frame
- Fix gradient color uninitialized stack memory (zero-init + clamped count)
- Fix shader time uniform integer division bug (float arithmetic)
- Add explicit VAO bind in onRender()
- Handle runtime surface config changes independently of shader changes
- Clamp negative gradient_count before GL upload

Follow-up
- Singleton API split (inst() + configure()) intentionally deferred to a
  dedicated PR because it changes the public constructor contract.
This commit is contained in:
Viktar Lukashonak
2026-07-13 19:50:09 +03:00
parent cf19c836d3
commit 5c979152de
8 changed files with 692 additions and 339 deletions
+46 -10
View File
@@ -1,36 +1,64 @@
#pragma once
#include <epoxy/gl.h>
#include <gtkmm/glarea.h>
#include <map>
#include <string>
#include <array>
#include <sigc++/connection.h>
#include "AModule.hpp"
#include "cava_backend.hpp"
namespace waybar::modules::cava {
class CavaGLSL final : public AModule, public Gtk::GLArea {
class CavaGLSL final : public AModule {
public:
CavaGLSL(const std::string&, const Json::Value&);
~CavaGLSL() = default;
~CavaGLSL();
auto doAction(const std::string& name) -> void override;
private:
using Action = void (CavaGLSL::*)();
Gtk::GLArea gl_area_;
std::shared_ptr<CavaBackend> backend_;
struct ::cava::config_params prm_;
int frame_counter{0};
// Cached config params (deep-copied strings to avoid dangling char* on backend reload)
int sdl_width_{0};
int sdl_height_{0};
int bar_width_{0};
int bar_spacing_{0};
int gradient_count_{0};
std::string vertex_shader_;
std::string fragment_shader_;
std::string bcolor_;
std::string color_;
std::array<std::string, 8> gradient_colors_;
int frame_counter_{0};
bool silence_{false};
bool hide_on_silence_{false};
bool mapped_{false};
// Cava method
auto onUpdate(const ::cava::audio_raw& input) -> void;
void pauseResume();
auto onUpdate(const CavaBackend::AudioRaw& input) -> void;
auto onSilence() -> void;
// Member variable to store the shared pointer
std::shared_ptr<::cava::audio_raw> m_data_;
GLuint shaderProgram_;
auto onBackendConfigChanged() -> void;
void cacheConfigParams(const ::cava::config_params& src);
// Member variable to store audio data
CavaBackend::AudioRaw m_data_;
GLuint shaderProgram_{0};
// OpenGL variables
GLuint fbo_;
GLuint texture_;
GLuint fbo_{0};
GLuint texture_{0};
GLuint vbo_{0};
GLuint ibo_{0};
GLuint vao_{0};
GLint uniform_bars_;
GLint uniform_previous_bars_;
GLint uniform_bars_count_;
GLint uniform_time_;
GLint uniform_input_texture_;
// Methods
void onRealize();
bool onRender(const Glib::RefPtr<Gdk::GLContext>& context);
@@ -39,5 +67,13 @@ class CavaGLSL final : public AModule, public Gtk::GLArea {
void initSurface();
void initGLSL();
GLuint loadShader(const std::string& fileName, GLenum type);
void cleanupGL();
// ModuleActionMap
static const std::map<std::string, Action> actionMap_;
sigc::connection audio_raw_update_conn_;
sigc::connection silence_conn_;
sigc::connection config_changed_conn_;
};
} // namespace waybar::modules::cava
+17 -9
View File
@@ -1,30 +1,38 @@
#pragma once
#include <map>
#include <string>
#include <sigc++/connection.h>
#include "ALabel.hpp"
#include "cava_backend.hpp"
namespace waybar::modules::cava {
class Cava final : public ALabel, public sigc::trackable {
class CavaRaw final : public ALabel {
public:
Cava(const std::string&, const Json::Value&);
~Cava() = default;
CavaRaw(const std::string&, const Json::Value&);
~CavaRaw();
auto doAction(const std::string& name) -> void override;
private:
using Action = void (CavaRaw::*)();
std::shared_ptr<CavaBackend> backend_;
// Text to display
Glib::ustring label_text_{""};
Glib::ustring label_text_;
bool silence_{false};
bool hide_on_silence_{false};
std::string format_silent_{""};
int ascii_range_{0};
std::string format_silent_;
// Cava method
void pause_resume();
void pauseResume();
auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void;
// ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()>
actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}};
static const std::map<std::string, Action> actionMap_;
sigc::connection update_conn_;
sigc::connection silence_conn_;
};
} // namespace waybar::modules::cava
+90 -21
View File
@@ -1,8 +1,17 @@
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <json/json.h>
#include <sigc++/sigc++.h>
#include "util/SafeSignal.hpp"
#include "util/sleeper_thread.hpp"
namespace cava {
@@ -21,7 +30,6 @@ extern "C" {
} // namespace cava
namespace waybar::modules::cava {
using namespace std::literals::chrono_literals;
class CavaBackend final {
public:
@@ -29,19 +37,38 @@ class CavaBackend final {
virtual ~CavaBackend();
// Methods
int getAsciiRange();
int getAsciiRange() const;
void doPauseResume();
void Update();
const struct ::cava::config_params* getPrm();
std::chrono::milliseconds getFrameTimeMilsec();
void update();
const ::cava::config_params& getPrm() const;
std::chrono::milliseconds getFrameTimeMilsec() const;
struct AudioRaw {
std::vector<float> bars_raw;
std::vector<float> previous_bars_raw;
int number_of_bars = 0;
AudioRaw() = default;
explicit AudioRaw(const ::cava::audio_raw& raw) {
number_of_bars = raw.number_of_bars;
if (raw.bars_raw != nullptr && number_of_bars > 0) {
bars_raw.assign(raw.bars_raw, raw.bars_raw + number_of_bars);
}
if (raw.previous_bars_raw != nullptr && number_of_bars > 0) {
previous_bars_raw.assign(raw.previous_bars_raw, raw.previous_bars_raw + number_of_bars);
}
}
};
// Signal accessor
using type_signal_update = sigc::signal<void(const std::string&)>;
type_signal_update signal_update();
using type_signal_audio_raw_update = sigc::signal<void(const ::cava::audio_raw&)>;
type_signal_audio_raw_update signal_audio_raw_update();
using type_signal_silence = sigc::signal<void()>;
type_signal_silence signal_silence();
using SignalUpdate = SafeSignal<const std::string&>;
SignalUpdate& signalUpdate();
using SignalAudioRawUpdate = SafeSignal<AudioRaw>;
SignalAudioRawUpdate& signalAudioRawUpdate();
using SignalSilence = SafeSignal<>;
SignalSilence& signalSilence();
using SignalConfigChanged = SafeSignal<>;
SignalConfigChanged& signalConfigChanged();
private:
CavaBackend(const Json::Value& config);
@@ -49,36 +76,78 @@ class CavaBackend final {
util::SleeperThread out_thread_;
// Cava API to read audio source
::cava::ptr input_source_{NULL};
::cava::ptr input_source_{nullptr};
struct ::cava::error_s error_{}; // cava errors
struct ::cava::config_params prm_{}; // cava parameters
struct ::cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct ::cava::audio_data audio_data_{}; // cava audio data
struct ::cava::cava_plan* plan_{NULL}; //{new cava_plan{}};
struct ::cava::cava_plan* plan_{nullptr}; //{new cava_plan{}};
std::chrono::seconds fetch_input_delay_{4};
// Delay to handle audio source
std::chrono::milliseconds frame_time_milsec_{1s};
const Json::Value& config_;
struct AdaptiveDelay {
std::chrono::milliseconds delay;
std::chrono::seconds delta{0};
explicit AdaptiveDelay(std::chrono::milliseconds initial = std::chrono::seconds(1))
: delay(initial) {}
bool increase() {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
return true;
}
return false;
}
bool decrease() {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
return true;
}
return false;
}
std::chrono::milliseconds current() const { return delay; }
void reset(std::chrono::milliseconds new_delay) {
delay = new_delay;
delta = std::chrono::seconds{0};
}
};
AdaptiveDelay adaptive_delay_;
Json::Value config_;
int re_paint_{0};
bool silence_{false};
bool silence_prev_{false};
std::chrono::seconds suspend_silence_delay_{0};
int sleep_counter_{0};
std::string output_{};
// Methods
void invoke();
void execute();
bool isSilence();
bool isSilent();
void doUpdate(bool force = false);
void loadConfig();
void freeBackend();
// Signal
type_signal_update m_signal_update_;
type_signal_audio_raw_update m_signal_audio_raw_;
type_signal_silence m_signal_silence_;
SignalUpdate m_signal_update_;
SignalAudioRawUpdate m_signal_audio_raw_;
SignalSilence m_signal_silence_;
SignalConfigChanged m_signal_config_changed_;
std::atomic<bool> shutdown_{false};
bool audio_raw_initialized_{false};
mutable std::recursive_mutex state_mutex_;
// Synchronization for joining read_thread_ during destruction
bool read_thread_exited_{false};
mutable std::mutex read_thread_exit_mutex_;
std::condition_variable read_thread_exit_cv_;
};
} // namespace waybar::modules::cava
+6 -4
View File
@@ -1,5 +1,7 @@
#pragma once
#include <memory>
#ifdef HAVE_LIBCAVA
#include "cavaRaw.hpp"
#include "cava_backend.hpp"
@@ -9,16 +11,16 @@
#endif
namespace waybar::modules::cava {
AModule* getModule(const std::string& id, const Json::Value& config) {
inline std::unique_ptr<AModule> getModule(const std::string& id, const Json::Value& config) {
#ifdef HAVE_LIBCAVA
const std::shared_ptr<CavaBackend> backend_{waybar::modules::cava::CavaBackend::inst(config)};
switch (backend_->getPrm()->output) {
switch (backend_->getPrm().output) {
#ifdef HAVE_LIBCAVAGLSL
case ::cava::output_method::OUTPUT_SDL_GLSL:
return new waybar::modules::cava::CavaGLSL(id, config);
return std::make_unique<waybar::modules::cava::CavaGLSL>(id, config);
#endif
default:
return new waybar::modules::cava::Cava(id, config);
return std::make_unique<waybar::modules::cava::CavaRaw>(id, config);
}
#else
throw std::runtime_error("Unknown module");
+1 -1
View File
@@ -372,7 +372,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
}
#endif
if (ref == "cava") {
return waybar::modules::cava::getModule(id, config_[name]);
return waybar::modules::cava::getModule(id, config_[name]).release();
}
#ifdef HAVE_SYSTEMD_MONITOR
if (ref == "systemd-failed-units") {
+259 -104
View File
@@ -2,88 +2,214 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
#include <vector>
const std::map<std::string, waybar::modules::cava::CavaGLSL::Action>
waybar::modules::cava::CavaGLSL::actionMap_{{"mode", &CavaGLSL::pauseResume}};
waybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config)
: AModule(config, "cavaGLSL", id, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} {
set_name(name_);
gl_area_.set_name(name_);
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (!id.empty()) {
get_style_context()->add_class(id);
gl_area_.get_style_context()->add_class(id);
}
get_style_context()->add_class(MODULE_CLASS);
gl_area_.get_style_context()->add_class(MODULE_CLASS);
set_use_es(true);
// set_auto_render(true);
signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize));
signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false);
gl_area_.set_use_es(true);
gl_area_.signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize));
gl_area_.signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false);
gl_area_.signal_map().connect([this]() { mapped_ = true; });
gl_area_.signal_unmap().connect([this]() { mapped_ = false; });
// Get parameters_config struct from the backend
prm_ = *backend_->getPrm();
cacheConfigParams(backend_->getPrm());
// Set widget length
int length{0};
if (config_["min-length"].isUInt())
length = config_["min-length"].asUInt();
else if (config_["max-length"].isUInt())
length = config_["max-length"].asUInt();
else
length = prm_.sdl_width;
length = sdl_width_;
set_size_request(length, prm_.sdl_height);
gl_area_.set_size_request(length, sdl_height_);
// Subscribe for changes
backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate));
// Subscribe for silence
backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence));
event_box_.add(*this);
audio_raw_update_conn_ =
backend_->signalAudioRawUpdate().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate));
silence_conn_ = backend_->signalSilence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence));
config_changed_conn_ =
backend_->signalConfigChanged().connect(sigc::mem_fun(*this, &CavaGLSL::onBackendConfigChanged));
event_box_.add(gl_area_);
}
auto waybar::modules::cava::CavaGLSL::onUpdate(const ::cava::audio_raw& input) -> void {
Glib::signal_idle().connect_once([this, input]() {
m_data_ = std::make_shared<::cava::audio_raw>(input);
if (silence_) {
get_style_context()->remove_class("silent");
if (!get_style_context()->has_class("updated")) get_style_context()->add_class("updated");
show();
silence_ = false;
}
waybar::modules::cava::CavaGLSL::~CavaGLSL() {
audio_raw_update_conn_.disconnect();
silence_conn_.disconnect();
config_changed_conn_.disconnect();
queue_render();
});
if (gl_area_.get_realized()) {
gl_area_.make_current();
cleanupGL();
}
}
void waybar::modules::cava::CavaGLSL::cleanupGL() {
if (shaderProgram_ != 0) {
glDeleteProgram(shaderProgram_);
shaderProgram_ = 0;
}
if (fbo_ != 0) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = 0;
}
if (texture_ != 0) {
glDeleteTextures(1, &texture_);
texture_ = 0;
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (ibo_ != 0) {
glDeleteBuffers(1, &ibo_);
ibo_ = 0;
}
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
}
auto waybar::modules::cava::CavaGLSL::doAction(const std::string& name) -> void {
auto it = actionMap_.find(name);
if (it != actionMap_.end() && it->second) {
(this->*it->second)();
} else {
spdlog::error("CavaGLSL. Unsupported action \"{0}\"", name);
}
}
void waybar::modules::cava::CavaGLSL::pauseResume() { backend_->doPauseResume(); }
auto waybar::modules::cava::CavaGLSL::onUpdate(const CavaBackend::AudioRaw& input) -> void {
m_data_ = input;
if (silence_) {
gl_area_.get_style_context()->remove_class("silent");
if (!gl_area_.get_style_context()->has_class("updated"))
gl_area_.get_style_context()->add_class("updated");
gl_area_.show();
silence_ = false;
}
if (mapped_) {
gl_area_.queue_render();
}
}
auto waybar::modules::cava::CavaGLSL::onSilence() -> void {
Glib::signal_idle().connect_once([this]() {
if (!silence_) {
if (get_style_context()->has_class("updated")) get_style_context()->remove_class("updated");
if (!silence_) {
if (gl_area_.get_style_context()->has_class("updated"))
gl_area_.get_style_context()->remove_class("updated");
if (hide_on_silence_) hide();
silence_ = true;
get_style_context()->add_class("silent");
// Set clear color to black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
queue_render();
if (hide_on_silence_) gl_area_.hide();
silence_ = true;
gl_area_.get_style_context()->add_class("silent");
}
}
void waybar::modules::cava::CavaGLSL::cacheConfigParams(const ::cava::config_params& src) {
sdl_width_ = src.sdl_width;
sdl_height_ = src.sdl_height;
bar_width_ = src.bar_width;
bar_spacing_ = src.bar_spacing;
gradient_count_ = src.gradient_count;
vertex_shader_ = src.vertex_shader ? src.vertex_shader : "";
fragment_shader_ = src.fragment_shader ? src.fragment_shader : "";
bcolor_ = src.bcolor ? src.bcolor : "";
color_ = src.color ? src.color : "";
for (size_t i = 0; i < gradient_colors_.size(); ++i) {
gradient_colors_[i] = (src.gradient_colors[i] ? src.gradient_colors[i] : "");
}
}
auto waybar::modules::cava::CavaGLSL::onBackendConfigChanged() -> void {
auto new_prm = backend_->getPrm();
bool dimensions_changed =
(new_prm.sdl_width != sdl_width_) || (new_prm.sdl_height != sdl_height_);
bool shaders_changed = false;
std::string new_vertex = new_prm.vertex_shader ? new_prm.vertex_shader : "";
std::string new_fragment = new_prm.fragment_shader ? new_prm.fragment_shader : "";
shaders_changed = (vertex_shader_ != new_vertex) || (fragment_shader_ != new_fragment);
bool surface_changed = false;
if (new_prm.bar_width != bar_width_) surface_changed = true;
if (new_prm.bar_spacing != bar_spacing_) surface_changed = true;
if (new_prm.gradient_count != gradient_count_) surface_changed = true;
std::string new_bcolor = new_prm.bcolor ? new_prm.bcolor : "";
if (bcolor_ != new_bcolor) surface_changed = true;
std::string new_color = new_prm.color ? new_prm.color : "";
if (color_ != new_color) surface_changed = true;
for (size_t i = 0; i < gradient_colors_.size(); ++i) {
std::string new_grad = new_prm.gradient_colors[i] ? new_prm.gradient_colors[i] : "";
if (gradient_colors_[i] != new_grad) {
surface_changed = true;
break;
}
});
}
cacheConfigParams(new_prm);
if ((dimensions_changed || shaders_changed) && gl_area_.get_realized()) {
gl_area_.make_current();
cleanupGL();
initShaders();
if (shaderProgram_ != 0) {
initGLSL();
initSurface();
}
} else if (surface_changed && gl_area_.get_realized()) {
gl_area_.make_current();
glUseProgram(shaderProgram_);
initSurface();
}
int length{0};
if (config_["min-length"].isUInt())
length = config_["min-length"].asUInt();
else if (config_["max-length"].isUInt())
length = config_["max-length"].asUInt();
else
length = sdl_width_;
gl_area_.set_size_request(length, sdl_height_);
}
bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr<Gdk::GLContext>& context) {
if (!m_data_) return true;
if (m_data_.bars_raw.empty() || shaderProgram_ == 0) return true;
glUseProgram(shaderProgram_);
glBindVertexArray(vao_);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_);
glUniform1i(glGetUniformLocation(shaderProgram_, "inputTexture"), 0);
glUniform1i(uniform_input_texture_, 0);
glUniform1fv(uniform_bars_, m_data_->number_of_bars, m_data_->bars_raw);
glUniform1fv(uniform_previous_bars_, m_data_->number_of_bars, m_data_->previous_bars_raw);
glUniform1i(uniform_bars_count_, m_data_->number_of_bars);
++frame_counter;
glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3);
glUniform1fv(uniform_bars_, m_data_.number_of_bars, m_data_.bars_raw.data());
glUniform1fv(uniform_previous_bars_, m_data_.number_of_bars, m_data_.previous_bars_raw.data());
glUniform1i(uniform_bars_count_, m_data_.number_of_bars);
++frame_counter_;
glUniform1f(uniform_time_,
static_cast<float>(frame_counter_) * backend_->getFrameTimeMilsec().count() / 1000.0f);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
@@ -94,21 +220,35 @@ bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr<Gdk::GLContext
}
void waybar::modules::cava::CavaGLSL::onRealize() {
make_current();
gl_area_.make_current();
cleanupGL();
initShaders();
if (shaderProgram_ == 0) {
return;
}
initGLSL();
initSurface();
}
struct colors {
struct Colors {
uint16_t R;
uint16_t G;
uint16_t B;
};
static void parse_color(char* color_string, struct colors* color) {
if (color_string[0] == '#') {
sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B);
static void parse_color(const char* color_string, struct Colors* color) {
if (color_string == nullptr) {
return;
}
if (color_string[0] != '#') {
return;
}
if (std::strlen(color_string) < 7) {
spdlog::warn("Invalid color string '{}': expected #RRGGBB", color_string);
return;
}
if (std::sscanf(color_string + 1, "%02hx%02hx%02hx", &color->R, &color->G, &color->B) != 3) {
spdlog::warn("Failed to parse color string '{}'", color_string);
}
}
@@ -123,49 +263,44 @@ void waybar::modules::cava::CavaGLSL::initGLSL() {
GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
GLint indexData[]{0, 1, 2, 3};
GLuint gVBO{0};
glGenBuffers(1, &gVBO);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glGenBuffers(1, &vbo_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);
GLuint gIBO{0};
glGenBuffers(1, &gIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glGenBuffers(1, &ibo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);
GLuint gVAO{0};
glGenVertexArrays(1, &gVAO);
glBindVertexArray(gVAO);
glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_);
glEnableVertexAttribArray(gVertexPos2DLocation);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create a texture to attach the framebuffer
glGenTextures(1, &texture_);
glBindTexture(GL_TEXTURE_2D, texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, prm_.sdl_width, prm_.sdl_height, 0, GL_RGBA,
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sdl_width_, sdl_height_, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0);
// Check is framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
spdlog::error("{0}. Framebuffer not complete", name_);
}
// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars");
uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars");
uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count");
uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time");
uniform_input_texture_ = glGetUniformLocation(shaderProgram_, "inputTexture");
GLuint err{glGetError()};
if (err != 0) {
@@ -174,32 +309,32 @@ void waybar::modules::cava::CavaGLSL::initGLSL() {
}
void waybar::modules::cava::CavaGLSL::initSurface() {
colors color = {0};
Colors color = {0};
GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")};
parse_color(prm_.bcolor, &color);
glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0,
(float)color.B / 255.0);
parse_color(bcolor_.c_str(), &color);
glUniform3f(uniform_bg_col, static_cast<float>(color.R) / 255.0f, static_cast<float>(color.G) / 255.0f,
static_cast<float>(color.B) / 255.0f);
GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")};
parse_color(prm_.color, &color);
glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0,
(float)color.B / 255.0);
parse_color(color_.c_str(), &color);
glUniform3f(uniform_fg_col, static_cast<float>(color.R) / 255.0f, static_cast<float>(color.G) / 255.0f,
static_cast<float>(color.B) / 255.0f);
GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")};
glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f);
glUniform3f(uniform_res, static_cast<float>(sdl_width_), static_cast<float>(sdl_height_), 0.0f);
GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")};
glUniform1i(uniform_bar_width, prm_.bar_width);
glUniform1i(uniform_bar_width, bar_width_);
GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")};
glUniform1i(uniform_bar_spacing, prm_.bar_spacing);
glUniform1i(uniform_bar_spacing, bar_spacing_);
GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "gradient_count")};
glUniform1i(uniform_gradient_count, prm_.gradient_count);
glUniform1i(uniform_gradient_count, std::max(0, gradient_count_));
GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, "gradient_colors")};
GLfloat gradient_colors[8][3];
for (int i{0}; i < prm_.gradient_count; ++i) {
parse_color(prm_.gradient_colors[i], &color);
gradient_colors[i][0] = (float)color.R / 255.0;
gradient_colors[i][1] = (float)color.G / 255.0;
gradient_colors[i][2] = (float)color.B / 255.0;
GLfloat gradient_colors[8][3] = {};
for (int i{0}; i < gradient_count_; ++i) {
parse_color(gradient_colors_[i].c_str(), &color);
gradient_colors[i][0] = static_cast<float>(color.R) / 255.0f;
gradient_colors[i][1] = static_cast<float>(color.G) / 255.0f;
gradient_colors[i][2] = static_cast<float>(color.B) / 255.0f;
}
glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors);
glUniform3fv(uniform_gradient_colors, std::max(0, std::min(gradient_count_, 8)), &gradient_colors[0][0]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
@@ -207,9 +342,23 @@ void waybar::modules::cava::CavaGLSL::initSurface() {
void waybar::modules::cava::CavaGLSL::initShaders() {
shaderProgram_ = glCreateProgram();
if (shaderProgram_ == 0) {
spdlog::error("{0}. Failed to create shader program", name_);
gl_area_.hide();
return;
}
GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)};
GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)};
GLuint vertexShader{loadShader(vertex_shader_, GL_VERTEX_SHADER)};
GLuint fragmentShader{loadShader(fragment_shader_, GL_FRAGMENT_SHADER)};
if (vertexShader == 0 || fragmentShader == 0) {
if (vertexShader != 0) glDeleteShader(vertexShader);
if (fragmentShader != 0) glDeleteShader(fragmentShader);
glDeleteProgram(shaderProgram_);
shaderProgram_ = 0;
gl_area_.hide();
return;
}
glAttachShader(shaderProgram_, vertexShader);
glAttachShader(shaderProgram_, fragmentShader);
@@ -219,14 +368,18 @@ void waybar::modules::cava::CavaGLSL::initShaders() {
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Check for linking errors
GLint success, len;
GLint success{0};
glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);
if (!success) {
GLint len{0};
glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len);
GLchar* infoLog{(char*)'\0'};
glGetProgramInfoLog(shaderProgram_, len, &len, infoLog);
spdlog::error("{0}. Shader linking error: {1}", name_, infoLog);
std::vector<GLchar> infoLog(len + 1);
glGetProgramInfoLog(shaderProgram_, len, nullptr, infoLog.data());
spdlog::error("{0}. Shader linking error: {1}", name_, infoLog.data());
glDeleteProgram(shaderProgram_);
shaderProgram_ = 0;
gl_area_.hide();
return;
}
glReleaseShaderCompiler();
@@ -236,35 +389,37 @@ void waybar::modules::cava::CavaGLSL::initShaders() {
GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) {
spdlog::debug("{0}. loadShader: {1}", name_, fileName);
// Read shader source code from the file
std::ifstream shaderFile{fileName};
if (!shaderFile.is_open()) {
spdlog::error("{0}. Could not open shader file: {1}", name_, fileName);
return 0;
}
std::ostringstream buffer;
buffer << shaderFile.rdbuf(); // read file content into stringstream
buffer << shaderFile.rdbuf();
std::string str{buffer.str()};
const char* shaderSource = str.c_str();
shaderFile.close();
GLuint shaderID{glCreateShader(type)};
if (shaderID == 0) spdlog::error("{0}. Error creating shader type: {0}", type);
if (shaderID == 0) {
spdlog::error("{0}. Error creating shader type: {1}", name_, type);
return 0;
}
const char* shaderSource = str.c_str();
glShaderSource(shaderID, 1, &shaderSource, nullptr);
glCompileShader(shaderID);
// Check for compilation errors
GLint success, len;
GLint success{0};
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
if (!success) {
GLint len{0};
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len);
GLchar* infoLog{(char*)'\0'};
glGetShaderInfoLog(shaderID, len, nullptr, infoLog);
spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog);
std::vector<GLchar> infoLog(len + 1);
glGetShaderInfoLog(shaderID, len, nullptr, infoLog.data());
spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog.data());
glDeleteShader(shaderID);
return 0;
}
return shaderID;
+48 -39
View File
@@ -2,59 +2,68 @@
#include <spdlog/spdlog.h>
waybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config)
const std::map<std::string, waybar::modules::cava::CavaRaw::Action>
waybar::modules::cava::CavaRaw::actionMap_{{"mode", &CavaRaw::pauseResume}};
waybar::modules::cava::CavaRaw::CavaRaw(const std::string& id, const Json::Value& config)
: ALabel(config, "cava", id, "{}", 60, false, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} {
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
ascii_range_ = backend_->getAsciiRange();
backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate));
backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence));
backend_->Update();
update_conn_ = backend_->signalUpdate().connect(sigc::mem_fun(*this, &CavaRaw::onUpdate));
silence_conn_ = backend_->signalSilence().connect(sigc::mem_fun(*this, &CavaRaw::onSilence));
backend_->update();
}
auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void {
if ((actionMap_[name])) {
(this->*actionMap_[name])();
} else
waybar::modules::cava::CavaRaw::~CavaRaw() {
update_conn_.disconnect();
silence_conn_.disconnect();
}
auto waybar::modules::cava::CavaRaw::doAction(const std::string& name) -> void {
auto it = actionMap_.find(name);
if (it != actionMap_.end() && it->second) {
(this->*it->second)();
} else {
spdlog::error("Cava. Unsupported action \"{0}\"", name);
}
}
// Cava actions
void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
Glib::signal_idle().connect_once([this, input]() {
if (silence_) {
silence_ = false;
label_.get_style_context()->remove_class("silent");
if (!label_.get_style_context()->has_class("updated"))
label_.get_style_context()->add_class("updated");
}
label_text_.clear();
for (auto& ch : input)
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1));
void waybar::modules::cava::CavaRaw::pauseResume() { backend_->doPauseResume(); }
auto waybar::modules::cava::CavaRaw::onUpdate(const std::string& input) -> void {
if (silence_) {
silence_ = false;
label_.get_style_context()->remove_class("silent");
if (!label_.get_style_context()->has_class("updated"))
label_.get_style_context()->add_class("updated");
}
label_text_.clear();
auto ascii_range = backend_->getAsciiRange();
for (auto& ch : input) {
auto uch = static_cast<unsigned char>(ch);
label_text_.append(getIcon((uch > ascii_range) ? ascii_range : uch, "", ascii_range + 1));
}
label_.set_markup(label_text_);
label_.show();
ALabel::update();
});
label_.set_markup(label_text_);
label_.show();
ALabel::update();
}
auto waybar::modules::cava::Cava::onSilence() -> void {
Glib::signal_idle().connect_once([this]() {
if (!silence_) {
if (label_.get_style_context()->has_class("updated"))
label_.get_style_context()->remove_class("updated");
auto waybar::modules::cava::CavaRaw::onSilence() -> void {
if (!silence_) {
if (label_.get_style_context()->has_class("updated"))
label_.get_style_context()->remove_class("updated");
if (hide_on_silence_) {
// Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout
label_.set_markup("");
label_.hide();
} else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
silence_ = true;
label_.get_style_context()->add_class("silent");
if (hide_on_silence_) {
// Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout
label_.set_markup("");
label_.hide();
} else if (!format_silent_.empty()) {
label_.set_markup(format_silent_);
}
});
silence_ = true;
label_.get_style_context()->add_class("silent");
}
}
+225 -151
View File
@@ -2,8 +2,23 @@
#include <spdlog/spdlog.h>
#include <algorithm>
#include <stdexcept>
namespace {
struct CavaConfigGuard {
::cava::config_params* prm;
bool released = false;
explicit CavaConfigGuard(::cava::config_params* p) : prm(p) {}
~CavaConfigGuard() {
if (!released && prm) {
free_config(prm);
}
}
void release() { released = true; }
};
} // namespace
std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(
const Json::Value& config) {
static auto* backend = new CavaBackend(config);
@@ -12,69 +27,84 @@ std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaB
}
waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) {
// Load waybar module config
loadConfig();
// Read audio source trough cava API. Cava orginizes this process via infinity loop
read_thread_ = [this] {
try {
input_source_(&audio_data_);
} catch (const std::runtime_error& e) {
spdlog::warn("Cava backend. Read source error: {0}", e.what());
while (read_thread_.isRunning()) {
try {
if (input_source_) {
input_source_(&audio_data_);
}
} catch (const std::exception& e) {
spdlog::warn("Cava backend. Read source error: {0}", e.what());
}
if (!read_thread_.isRunning()) break;
read_thread_.sleep_for(fetch_input_delay_);
if (!read_thread_.isRunning()) break;
try {
loadConfig();
} catch (const std::exception& e) {
spdlog::error("{}", e.what());
}
}
read_thread_.sleep_for(fetch_input_delay_);
// loadConfig() now throws on failure; contain it so a runtime config error
// logs instead of terminating the process (#4456).
try {
loadConfig();
} catch (const std::exception& e) {
spdlog::error("{}", e.what());
{
std::lock_guard<std::mutex> lk(read_thread_exit_mutex_);
read_thread_exited_ = true;
}
read_thread_exit_cv_.notify_one();
};
// Write outcoming data. Emit signals
out_thread_ = [this] {
doUpdate(false);
out_thread_.sleep_for(frame_time_milsec_);
try {
doUpdate(false);
} catch (const std::exception& e) {
spdlog::error("Cava backend. Output thread error: {0}", e.what());
}
out_thread_.sleep_for(adaptive_delay_.current());
};
}
waybar::modules::cava::CavaBackend::~CavaBackend() {
{
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
shutdown_ = true;
}
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
out_thread_.stop();
read_thread_.stop();
std::unique_lock<std::mutex> lk(read_thread_exit_mutex_);
if (!read_thread_exit_cv_.wait_for(lk, std::chrono::milliseconds(100),
[this] { return read_thread_exited_; })) {
spdlog::debug(
"Cava backend: read thread still blocked in input_source() on shutdown. "
"Proceeding with cleanup.");
}
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
freeBackend();
}
static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1};
delay += delta;
return true;
}
return false;
}
static bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) {
delay -= delta;
delta -= std::chrono::seconds{1};
return true;
}
return false;
}
bool waybar::modules::cava::CavaBackend::isSilence() {
bool waybar::modules::cava::CavaBackend::isSilent() {
pthread_mutex_lock(&audio_data_.lock);
bool silent = true;
for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) {
return false;
silent = false;
break;
}
}
return true;
pthread_mutex_unlock(&audio_data_.lock);
return silent;
}
int waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; }
int waybar::modules::cava::CavaBackend::getAsciiRange() const {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
return prm_.ascii_range;
}
// Process: execute cava
void waybar::modules::cava::CavaBackend::invoke() {
pthread_mutex_lock(&audio_data_.lock);
::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out,
@@ -83,7 +113,6 @@ void waybar::modules::cava::CavaBackend::invoke() {
pthread_mutex_unlock(&audio_data_.lock);
}
// Do transformation under raw data
void waybar::modules::cava::CavaBackend::execute() {
invoke();
audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_);
@@ -99,183 +128,228 @@ void waybar::modules::cava::CavaBackend::execute() {
}
void waybar::modules::cava::CavaBackend::doPauseResume() {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
pthread_mutex_lock(&audio_data_.lock);
if (audio_data_.suspendFlag) {
audio_data_.suspendFlag = false;
pthread_cond_broadcast(&audio_data_.resumeCond);
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
adaptive_delay_.decrease();
} else {
audio_data_.suspendFlag = true;
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
adaptive_delay_.increase();
}
pthread_mutex_unlock(&audio_data_.lock);
Update();
update();
}
waybar::modules::cava::CavaBackend::type_signal_update
waybar::modules::cava::CavaBackend::signal_update() {
waybar::modules::cava::CavaBackend::SignalUpdate&
waybar::modules::cava::CavaBackend::signalUpdate() {
return m_signal_update_;
}
waybar::modules::cava::CavaBackend::type_signal_audio_raw_update
waybar::modules::cava::CavaBackend::signal_audio_raw_update() {
waybar::modules::cava::CavaBackend::SignalAudioRawUpdate&
waybar::modules::cava::CavaBackend::signalAudioRawUpdate() {
return m_signal_audio_raw_;
}
waybar::modules::cava::CavaBackend::type_signal_silence
waybar::modules::cava::CavaBackend::signal_silence() {
waybar::modules::cava::CavaBackend::SignalSilence&
waybar::modules::cava::CavaBackend::signalSilence() {
return m_signal_silence_;
}
void waybar::modules::cava::CavaBackend::Update() { doUpdate(true); }
waybar::modules::cava::CavaBackend::SignalConfigChanged&
waybar::modules::cava::CavaBackend::signalConfigChanged() {
return m_signal_config_changed_;
}
void waybar::modules::cava::CavaBackend::update() { doUpdate(true); }
void waybar::modules::cava::CavaBackend::doUpdate(bool force) {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
if (!plan_ || !input_source_) return;
if (audio_data_.suspendFlag && !force) return;
silence_ = isSilence();
silence_ = isSilent();
if (!silence_) sleep_counter_ = 0;
if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <=
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
static_cast<int>(std::chrono::milliseconds(prm_.sleep_timer * std::chrono::seconds(1)) /
adaptive_delay_.current())) {
++sleep_counter_;
silence_ = false;
}
}
if (!silence_ || prm_.sleep_timer == 0) {
if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
while (adaptive_delay_.decrease()) {}
execute();
if (re_paint_ == 1 || force || prm_.continuous_rendering) {
m_signal_update_.emit(output_);
m_signal_audio_raw_.emit(audio_raw_);
m_signal_audio_raw_.emit(AudioRaw{audio_raw_});
}
} else {
if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
while (adaptive_delay_.increase()) {}
if (silence_ != silence_prev_ || force) m_signal_silence_.emit();
}
silence_prev_ = silence_;
}
void waybar::modules::cava::CavaBackend::freeBackend() {
if (plan_ != NULL) {
input_source_ = nullptr;
if (plan_ != nullptr) {
cava_destroy(plan_);
plan_ = NULL;
plan_ = nullptr;
}
audio_raw_clean(&audio_raw_);
if (audio_raw_initialized_) {
audio_raw_clean(&audio_raw_);
audio_raw_initialized_ = false;
}
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
free_config(&prm_);
prm_ = {};
free(audio_data_.source);
audio_data_.source = nullptr;
free(audio_data_.cava_in);
audio_data_.cava_in = nullptr;
}
void waybar::modules::cava::CavaBackend::loadConfig() {
freeBackend();
// Load waybar module config
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
if (shutdown_.load()) {
return;
}
if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data());
// Load cava config
const Json::Value& cfg = config_;
struct ::cava::config_params new_prm{};
CavaConfigGuard new_prm_guard(&new_prm);
std::vector<char> cfgPath(PATH_MAX, '\0');
if (cfg["cava_config"].isString()) {
const std::string& s = cfg["cava_config"].asString();
auto len = std::min(s.size(), static_cast<size_t>(PATH_MAX - 1));
std::copy_n(s.begin(), len, cfgPath.begin());
cfgPath[len] = '\0';
}
error_.length = 0;
if (!load_config(cfgPath, &prm_, &error_)) {
// Throw, don't exit(): a bad config must disable only the cava module, not
// kill the whole bar (#4456). Caught by the factory (ctor) / read_thread_.
if (!load_config(cfgPath.data(), &new_prm, &error_)) {
throw std::runtime_error(std::string{"cava backend: error loading config: "} + error_.message);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
auto const output{prm_.output};
// prm_.output = ::cava::output_method::OUTPUT_RAW;
if (prm_.data_format) free(prm_.data_format);
// Default to ascii for format-icons output; allow user override
prm_.data_format = strdup(
config_["data_format"].isString() ? config_["data_format"].asString().c_str() : "ascii");
if (config_["raw_target"].isString()) {
if (prm_.raw_target) free(prm_.raw_target);
prm_.raw_target = strdup(config_["raw_target"].asString().c_str());
new_prm.inAtty = 0;
auto const output{new_prm.output};
if (new_prm.data_format) free(new_prm.data_format);
new_prm.data_format = strdup(
cfg["data_format"].isString() ? cfg["data_format"].asString().c_str() : "ascii");
if (cfg["raw_target"].isString()) {
if (new_prm.raw_target) free(new_prm.raw_target);
new_prm.raw_target = strdup(cfg["raw_target"].asString().c_str());
}
prm_.ascii_range = config_["format-icons"].size() - 1;
if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt();
if (config_["bar_width"].isInt()) prm_.bar_width = config_["bar_width"].asInt();
if (config_["bar_height"].isInt()) prm_.bar_height = config_["bar_height"].asInt();
prm_.orientation = ::cava::ORIENT_TOP;
prm_.xaxis = ::cava::xaxis_scale::NONE;
prm_.mono_opt = ::cava::AVERAGE;
prm_.autobars = 0;
if (config_["gravity"].isInt()) prm_.gravity = config_["gravity"].asInt();
if (config_["integral"].isInt()) prm_.integral = config_["integral"].asInt();
if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt();
// Calculate delay for Update() thread
frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt();
if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt();
if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt();
if (config_["lower_cutoff_freq"].isNumeric())
prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt();
if (config_["higher_cutoff_freq"].isNumeric())
prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt();
if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt();
if (config_["method"].isString())
prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str());
if (config_["source"].isString()) {
if (prm_.audio_source) free(prm_.audio_source);
prm_.audio_source = strdup(config_["source"].asString().c_str());
}
if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt();
if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt();
if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool();
if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool();
if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt();
if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool();
if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool();
if (config_["noise_reduction"].isDouble())
prm_.noise_reduction = config_["noise_reduction"].asDouble();
if (config_["input_delay"].isInt())
fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt());
if (config_["gradient"].isInt()) prm_.gradient = config_["gradient"].asInt();
if (prm_.gradient == 0)
prm_.gradient_count = 0;
else if (config_["gradient_count"].isInt())
prm_.gradient_count = config_["gradient_count"].asInt();
if (config_["sdl_width"].isInt()) prm_.sdl_width = config_["sdl_width"].asInt();
if (config_["sdl_height"].isInt()) prm_.sdl_height = config_["sdl_height"].asInt();
audio_raw_.height = prm_.ascii_range;
audio_data_.format = -1;
audio_data_.rate = 0;
audio_data_.samples_counter = 0;
audio_data_.channels = 2;
audio_data_.IEEE_FLOAT = 0;
audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &prm_);
if (!input_source_) {
throw std::runtime_error("cava backend: API didn't provide an input audio source");
{
auto icon_count = cfg["format-icons"].size();
new_prm.ascii_range = (icon_count > 0) ? static_cast<int>(icon_count) - 1 : 0;
}
prm_.output = ::cava::output_method::OUTPUT_RAW;
if (cfg["bar_spacing"].isInt()) new_prm.bar_spacing = cfg["bar_spacing"].asInt();
if (cfg["bar_width"].isInt()) new_prm.bar_width = cfg["bar_width"].asInt();
if (cfg["bar_height"].isInt()) new_prm.bar_height = cfg["bar_height"].asInt();
new_prm.orientation = ::cava::ORIENT_TOP;
new_prm.xaxis = ::cava::xaxis_scale::NONE;
new_prm.mono_opt = ::cava::AVERAGE;
new_prm.autobars = 0;
if (cfg["gravity"].isInt()) new_prm.gravity = cfg["gravity"].asInt();
if (cfg["integral"].isInt()) new_prm.integral = cfg["integral"].asInt();
// Make cava parameters configuration
// Init cava plan, audio_raw structure
audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_);
if (!plan_) spdlog::error("cava backend plan is not provided");
audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message
if (cfg["framerate"].isInt()) new_prm.framerate = cfg["framerate"].asInt();
if (cfg["autosens"].isInt()) new_prm.autosens = cfg["autosens"].asInt();
if (cfg["sensitivity"].isInt()) new_prm.sens = cfg["sensitivity"].asInt();
if (cfg["bars"].isInt()) new_prm.fixedbars = cfg["bars"].asInt();
if (cfg["lower_cutoff_freq"].isNumeric())
new_prm.lower_cut_off = cfg["lower_cutoff_freq"].asLargestInt();
if (cfg["higher_cutoff_freq"].isNumeric())
new_prm.upper_cut_off = cfg["higher_cutoff_freq"].asLargestInt();
if (cfg["sleep_timer"].isInt()) new_prm.sleep_timer = cfg["sleep_timer"].asInt();
if (cfg["method"].isString())
new_prm.input = ::cava::input_method_by_name(cfg["method"].asString().c_str());
if (cfg["source"].isString()) {
if (new_prm.audio_source) free(new_prm.audio_source);
new_prm.audio_source = strdup(cfg["source"].asString().c_str());
}
if (cfg["sample_rate"].isNumeric()) new_prm.samplerate = cfg["sample_rate"].asLargestInt();
if (cfg["sample_bits"].isInt()) new_prm.samplebits = cfg["sample_bits"].asInt();
if (cfg["stereo"].isBool()) new_prm.stereo = cfg["stereo"].asBool();
if (cfg["reverse"].isBool()) new_prm.reverse = cfg["reverse"].asBool();
if (cfg["bar_delimiter"].isInt()) new_prm.bar_delim = cfg["bar_delimiter"].asInt();
if (cfg["monstercat"].isBool()) new_prm.monstercat = cfg["monstercat"].asBool();
if (cfg["waves"].isBool()) new_prm.waves = cfg["waves"].asBool();
if (cfg["noise_reduction"].isDouble())
new_prm.noise_reduction = cfg["noise_reduction"].asDouble();
if (cfg["input_delay"].isInt())
fetch_input_delay_ = std::chrono::seconds(cfg["input_delay"].asInt());
if (cfg["gradient"].isInt()) new_prm.gradient = cfg["gradient"].asInt();
if (new_prm.gradient == 0)
new_prm.gradient_count = 0;
else if (cfg["gradient_count"].isInt())
new_prm.gradient_count = cfg["gradient_count"].asInt();
if (cfg["sdl_width"].isInt()) new_prm.sdl_width = cfg["sdl_width"].asInt();
if (cfg["sdl_height"].isInt()) new_prm.sdl_height = cfg["sdl_height"].asInt();
if (new_prm.framerate <= 0) {
throw std::runtime_error(std::string{"cava backend: framerate must be positive, got: "} +
std::to_string(new_prm.framerate));
}
adaptive_delay_.reset(std::chrono::milliseconds(static_cast<int>(1e3 / new_prm.framerate)));
freeBackend();
try {
audio_raw_.height = new_prm.ascii_range;
audio_data_.format = -1;
audio_data_.rate = 0;
audio_data_.samples_counter = 0;
audio_data_.channels = 2;
audio_data_.IEEE_FLOAT = 0;
audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
audio_data_.terminate = 0;
audio_data_.suspendFlag = false;
input_source_ = get_input(&audio_data_, &new_prm);
if (!input_source_) {
throw std::runtime_error("cava backend: API didn't provide an input audio source");
}
new_prm.output = ::cava::output_method::OUTPUT_RAW;
audio_raw_init(&audio_data_, &audio_raw_, &new_prm, &plan_);
if (!plan_) {
throw std::runtime_error("cava backend plan is not provided");
}
audio_raw_.previous_frame[0] = -1;
audio_raw_initialized_ = true;
} catch (...) {
freeBackend();
throw;
}
prm_ = new_prm;
new_prm_guard.release();
prm_.output = output;
m_signal_config_changed_.emit();
}
const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; }
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() {
return frame_time_milsec_;
};
const ::cava::config_params& waybar::modules::cava::CavaBackend::getPrm() const {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
return prm_;
}
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() const {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
return adaptive_delay_.current();
}