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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user