mirror of
https://github.com/Alexays/Waybar.git
synced 2026-08-18 11:07:06 +00:00
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.
427 lines
14 KiB
C++
427 lines
14 KiB
C++
#include "modules/cava/cavaGLSL.hpp"
|
|
|
|
#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)} {
|
|
gl_area_.set_name(name_);
|
|
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
|
|
if (!id.empty()) {
|
|
gl_area_.get_style_context()->add_class(id);
|
|
}
|
|
gl_area_.get_style_context()->add_class(MODULE_CLASS);
|
|
|
|
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; });
|
|
|
|
cacheConfigParams(backend_->getPrm());
|
|
|
|
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_);
|
|
|
|
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_);
|
|
}
|
|
|
|
waybar::modules::cava::CavaGLSL::~CavaGLSL() {
|
|
audio_raw_update_conn_.disconnect();
|
|
silence_conn_.disconnect();
|
|
config_changed_conn_.disconnect();
|
|
|
|
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 {
|
|
if (!silence_) {
|
|
if (gl_area_.get_style_context()->has_class("updated"))
|
|
gl_area_.get_style_context()->remove_class("updated");
|
|
|
|
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_.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(uniform_input_texture_, 0);
|
|
|
|
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);
|
|
|
|
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
void waybar::modules::cava::CavaGLSL::onRealize() {
|
|
gl_area_.make_current();
|
|
cleanupGL();
|
|
initShaders();
|
|
if (shaderProgram_ == 0) {
|
|
return;
|
|
}
|
|
initGLSL();
|
|
initSurface();
|
|
}
|
|
|
|
struct Colors {
|
|
uint16_t R;
|
|
uint16_t G;
|
|
uint16_t 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);
|
|
}
|
|
}
|
|
|
|
void waybar::modules::cava::CavaGLSL::initGLSL() {
|
|
GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, "vertexPosition_modelspace")};
|
|
if (gVertexPos2DLocation == -1) {
|
|
spdlog::error("{0}. Could not find vertex position shader variable", name_);
|
|
}
|
|
|
|
glClearColor(0.f, 0.f, 0.f, 1.f);
|
|
|
|
GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
|
|
GLint indexData[]{0, 1, 2, 3};
|
|
|
|
glGenBuffers(1, &vbo_);
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);
|
|
|
|
glGenBuffers(1, &ibo_);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);
|
|
|
|
glGenVertexArrays(1, &vao_);
|
|
glBindVertexArray(vao_);
|
|
glEnableVertexAttribArray(gVertexPos2DLocation);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
|
|
|
glGenFramebuffers(1, &fbo_);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
|
|
glGenTextures(1, &texture_);
|
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
|
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);
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
|
spdlog::error("{0}. Framebuffer not complete", name_);
|
|
}
|
|
|
|
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) {
|
|
spdlog::error("{0}. Error on initGLSL: {1}", name_, err);
|
|
}
|
|
}
|
|
|
|
void waybar::modules::cava::CavaGLSL::initSurface() {
|
|
Colors color = {0};
|
|
GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")};
|
|
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(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, static_cast<float>(sdl_width_), static_cast<float>(sdl_height_), 0.0f);
|
|
GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")};
|
|
glUniform1i(uniform_bar_width, bar_width_);
|
|
GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")};
|
|
glUniform1i(uniform_bar_spacing, bar_spacing_);
|
|
GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "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 < 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, 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);
|
|
}
|
|
|
|
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(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);
|
|
|
|
glLinkProgram(shaderProgram_);
|
|
|
|
glDeleteShader(vertexShader);
|
|
glDeleteShader(fragmentShader);
|
|
|
|
GLint success{0};
|
|
glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);
|
|
if (!success) {
|
|
GLint len{0};
|
|
glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len);
|
|
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();
|
|
glUseProgram(shaderProgram_);
|
|
}
|
|
|
|
GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) {
|
|
spdlog::debug("{0}. loadShader: {1}", name_, fileName);
|
|
|
|
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();
|
|
std::string str{buffer.str()};
|
|
shaderFile.close();
|
|
|
|
GLuint shaderID{glCreateShader(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);
|
|
|
|
GLint success{0};
|
|
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
|
|
if (!success) {
|
|
GLint len{0};
|
|
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len);
|
|
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;
|
|
}
|