Merge pull request #5199 from LukashonakV/cavaImpr_2

refactor(cava): fix thread-safety, resource leaks, and style violations
This commit is contained in:
Alexis Rouillard
2026-07-15 15:23:43 +02:00
committed by GitHub
10 changed files with 981 additions and 501 deletions
+142
View File
@@ -43,3 +43,145 @@ add its man page and a line in [`.github/wiki/mapping.json`](.github/wiki/mappin
- Build and test against the module(s) you touched. - Build and test against the module(s) you touched.
Have fun :) Have fun :)
---
# Coding Conventions
## 1. Language & Build
- **Standard**: C++20.
- **Build system**: Meson (`meson.build`). Project version is defined there.
- **Compiler flags**: Added via `add_project_arguments()` in Meson. Feature flags use `HAVE_*` / `WANT_*` prefixes (e.g. `-DHAVE_NIRI`, `-DHAVE_HYPRLAND`, `-DHAVE_LIBUDEV`).
## 2. Formatting
- **Tool**: `.clang-format` is checked in. **Never bypass it.**
- **Style**: Google base style.
- **Indent**: 2 spaces. No tabs.
- **Column limit**: 100.
- **Braces**: K&R (opening brace on the same line).
- **Declaration alignment**: Disabled (`AlignConsecutiveDeclarations: false`).
- **Pointer/reference alignment**: Left (`const Json::Value& config`, `int* ptr`, not `int *ptr`).
## 3. Naming
### Files
- Match the primary exported class exactly: `AAppIconLabel.hpp`, `workspaces.cpp`, `backlight_backend.hpp`.
- Corresponding header and source should live in predictable paths:
- `include/<module/path>.hpp`
- `src/<module/path>.cpp`
### Types
- **Classes / Structs**: `PascalCase`.
- Abstract base classes are prefixed with `A` (e.g., `AModule`, `ALabel`, `AIconLabel`, `AAppIconLabel`).
- **Enums / Enum classes**: `PascalCase` name.
- Enumerators: `UPPER_SNAKE_CASE` (e.g., `SCROLL_DIR::NONE`, `KillSignalAction::RELOAD`, `ChangeType::Increase`).
- **Concepts / Type aliases**: `PascalCase`.
### Variables
- **Member variables**: `snake_case_` with a **trailing underscore**.
- Examples: `config_`, `bar_`, `label_`, `app_icon_size_`, `distance_scrolled_y_`, `on_updated_cb_`.
- **Function parameters & locals**: `snake_case` (no trailing underscore).
- Examples: `workspace_data`, `should_refresh`, `app_identifier`, `preferred_device`.
- **Static / constexpr constants**: `UPPER_SNAKE_CASE` or descriptive `kPascalCase`.
- Examples: `MODULE_CLASS`, `EPOLL_MAX_EVENTS`, `kExecFailureExitCode`.
### Functions & Methods
- **Free functions**: `snake_case`.
- Examples: `sanitize_string()`, `rewrite_string()`, `get_total_memory()`, `best_device()`.
- **Class methods**: `lowerCamelCase`.
- Examples: `update()`, `tooltipEnabled()`, `handleScroll()`, `getScrollDir()`, `resolveFormat()`, `setBrightness()`.
- **Virtual overrides**: Mark with `override` (and `final` where applicable). Header signatures often use a trailing return type:
```cpp
auto update() -> void override;
auto refresh(int should_refresh) -> void;
```
### Namespaces
- All lowercase, nested by module path:
```cpp
namespace waybar { }
namespace waybar::modules::niri { }
namespace waybar::util { }
```
- Close every namespace with a comment:
```cpp
} // namespace waybar::modules::niri
```
## 4. Includes & Headers
- Use `#pragma once` in all project headers.
- Include order in `.cpp` files:
1. Corresponding header first.
2. Blank line.
3. External library headers (`<fmt/...>`, `<spdlog/...>`, `<gtkmm/...>`, `<json/json.h>`).
4. Standard library headers (`<algorithm>`, `<vector>`, `<memory>`).
5. Blank line.
6. Other project headers (`"util/..."`, `"modules/..."`).
- Do not use `using namespace` in headers. In `.cpp` files it is acceptable for narrow scopes (e.g., `using namespace std::literals::chrono_literals;`).
- Headers that expose standard-library types in their public interface (e.g. `std::chrono::milliseconds` as a return type or `std::vector<T>` as a member) must `#include` the corresponding standard header directly. Do not rely on transitive includes from other headers.
## 5. Class & Module Design
### Base Class Patterns
- All UI modules ultimately derive from `AModule` (and often `ALabel` or `AIconLabel`).
- Accept configuration in constructors:
```cpp
MyModule(const Json::Value& config, const std::string& name, const std::string& id, ...);
```
### Signals & Threading
- Use `Glib::Dispatcher` (via `waybar::SafeSignal`) to marshal work to the GTK main thread.
- Use `sigc::signal` for normal GTK++ signals.
- If a scope must not be interrupted by `pthread_cancel`, guard it with `waybar::util::CancellationGuard`.
### State / IPC
- Modules that talk to a compositor often implement a small `EventHandler` interface (`onEvent(...)`) and delegate to a singleton backend (e.g., `gIPC`).
### RAII
- Prefer `std::unique_ptr` with custom deleters over raw `new/delete` for C-API resources (see `ScopedFd`, `UdevDeleter`, `UdevDeviceDeleter`, `ScopeGuard`).
## 6. JSON Configuration
- Every module receives `const Json::Value& config` (usually as the first constructor argument).
- Always validate node type before reading:
```cpp
if (config_["sort-by-id"].isBool()) { ... }
if (config.isMember("window-rewrite-default") && config["window-rewrite-default"].isString()) { ... }
```
- Use `waybar::util::JsonParser` if you need to pre-process JSON with non-standard escape sequences.
## 7. String & UI Formatting
- Use `fmt::format` / `fmt::join` for all string composition.
- Use `fmt::dynamic_format_arg_store<fmt::format_context>` when building arguments dynamically.
- Custom `fmt::formatter` specializations are allowed for domain types (e.g., `Glib::ustring`, project enums).
- Sanitize arbitrary text before inserting into Pango markup with `waybar::util::sanitize_string`.
- Use `waybar::util::rewriteString` for user-configurable regex rewrites.
- Truncate UTF-8 safely with `waybar::util::utf8_truncate` / `utf8_width`.
## 8. Error Handling & Logging
- Use `spdlog` for all logging:
- `spdlog::error("Context: {}", e.what());`
- `spdlog::warn("Deprecated key '{}', prefer '{}'", old, replacement);`
- `spdlog::debug("State changed to {}", value);`
- Throw `std::runtime_error` (or similar) for fatal initialization failures that should bubble up to `main()`.
## 9. GTK / Glib Patterns
- Prefer gtkmm-3.0 types (`Gtk::Button`, `Gtk::Label`, `Gdk::Pixbuf`, `Glib::RefPtr`, `Glib::ustring`) over raw C GTK APIs.
- Access the default icon theme through thread-safe wrappers if off the main thread (`DefaultGtkIconThemeWrapper`).
- Tooltips and labels should respect the module `tooltip` toggle (see `tooltipEnabled()` in `AModule`).
## 10. Platform Portability
- Isolate platform-specific code in dedicated files (e.g., `linux.cpp`, `bsd.cpp`).
- Use preprocessor guards for platform differences (`#if defined(__FreeBSD__)`, `#if defined(HAVE_LIBNL)`).
- Keep the common interface in a shared header or base class.
## 11. Thread Safety & Cross-Thread Communication
- GTK is strictly single-threaded. Never emit raw `sigc::signal` from background threads.
- Use `waybar::SafeSignal<T...>` to marshal events from worker threads to the GTK main loop.
- When a module manages background threads, use `std::mutex`, `std::recursive_mutex`, or atomic variables to protect shared state, and ensure the destructor joins or synchronizes with those threads before destroying resources.
## 12. Unsafe Patterns to Avoid
- Do not use `strcpy`, `strcat`, or `sprintf` into fixed-size buffers (e.g. `char buf[PATH_MAX]`). Prefer `std::string`, `std::vector<char>`, or `std::array` with bounds-safe operations.
- When passing a `std::vector<char>` buffer to a C API that expects a mutable `char*` string, always ensure the buffer is null-terminated and clamp the written length to `size() - 1`. Never use `std::copy` from an unbounded source into a fixed-size buffer.
## 13. Singleton Lifetime
- Singletons or objects with process-wide lifetime must not store references (`&`) or pointers to objects with shorter lifetime (e.g., configuration trees, GTK widgets, or bar instances) unless they are explicitly notified of destruction. Prefer storing configuration by value (`Json::Value`, `std::string`, etc.) if the singleton outlives the config loader.
+46 -10
View File
@@ -1,36 +1,64 @@
#pragma once #pragma once
#include <epoxy/gl.h> #include <epoxy/gl.h>
#include <gtkmm/glarea.h>
#include <map>
#include <string>
#include <array>
#include <sigc++/connection.h>
#include "AModule.hpp" #include "AModule.hpp"
#include "cava_backend.hpp" #include "cava_backend.hpp"
namespace waybar::modules::cava { namespace waybar::modules::cava {
class CavaGLSL final : public AModule, public Gtk::GLArea { class CavaGLSL final : public AModule {
public: public:
CavaGLSL(const std::string&, const Json::Value&); CavaGLSL(const std::string&, const Json::Value&);
~CavaGLSL() = default; ~CavaGLSL();
auto doAction(const std::string& name) -> void override;
private: private:
using Action = void (CavaGLSL::*)();
Gtk::GLArea gl_area_;
std::shared_ptr<CavaBackend> backend_; std::shared_ptr<CavaBackend> backend_;
struct ::cava::config_params prm_; // Cached config params (deep-copied strings to avoid dangling char* on backend reload)
int frame_counter{0}; 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 silence_{false};
bool hide_on_silence_{false}; bool hide_on_silence_{false};
bool mapped_{false};
// Cava method // Cava method
auto onUpdate(const ::cava::audio_raw& input) -> void; void pauseResume();
auto onUpdate(const CavaBackend::AudioRaw& input) -> void;
auto onSilence() -> void; auto onSilence() -> void;
// Member variable to store the shared pointer auto onBackendConfigChanged() -> void;
std::shared_ptr<::cava::audio_raw> m_data_; void cacheConfigParams(const ::cava::config_params& src);
GLuint shaderProgram_; // Member variable to store audio data
CavaBackend::AudioRaw m_data_;
GLuint shaderProgram_{0};
// OpenGL variables // OpenGL variables
GLuint fbo_; GLuint fbo_{0};
GLuint texture_; GLuint texture_{0};
GLuint vbo_{0};
GLuint ibo_{0};
GLuint vao_{0};
GLint uniform_bars_; GLint uniform_bars_;
GLint uniform_previous_bars_; GLint uniform_previous_bars_;
GLint uniform_bars_count_; GLint uniform_bars_count_;
GLint uniform_time_; GLint uniform_time_;
GLint uniform_input_texture_;
// Methods // Methods
void onRealize(); void onRealize();
bool onRender(const Glib::RefPtr<Gdk::GLContext>& context); bool onRender(const Glib::RefPtr<Gdk::GLContext>& context);
@@ -39,5 +67,13 @@ class CavaGLSL final : public AModule, public Gtk::GLArea {
void initSurface(); void initSurface();
void initGLSL(); void initGLSL();
GLuint loadShader(const std::string& fileName, GLenum type); 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 } // namespace waybar::modules::cava
+17 -9
View File
@@ -1,30 +1,38 @@
#pragma once #pragma once
#include <map>
#include <string>
#include <sigc++/connection.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "cava_backend.hpp" #include "cava_backend.hpp"
namespace waybar::modules::cava { namespace waybar::modules::cava {
class Cava final : public ALabel, public sigc::trackable { class CavaRaw final : public ALabel {
public: public:
Cava(const std::string&, const Json::Value&); CavaRaw(const std::string&, const Json::Value&);
~Cava() = default; ~CavaRaw();
auto doAction(const std::string& name) -> void override; auto doAction(const std::string& name) -> void override;
private: private:
using Action = void (CavaRaw::*)();
std::shared_ptr<CavaBackend> backend_; std::shared_ptr<CavaBackend> backend_;
// Text to display // Text to display
Glib::ustring label_text_{""}; Glib::ustring label_text_;
bool silence_{false}; bool silence_{false};
bool hide_on_silence_{false}; bool hide_on_silence_{false};
std::string format_silent_{""}; std::string format_silent_;
int ascii_range_{0};
// Cava method // Cava method
void pause_resume(); void pauseResume();
auto onUpdate(const std::string& input) -> void; auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void; auto onSilence() -> void;
// ModuleActionMap // ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()> static const std::map<std::string, Action> actionMap_;
actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}};
sigc::connection update_conn_;
sigc::connection silence_conn_;
}; };
} // namespace waybar::modules::cava } // namespace waybar::modules::cava
+90 -21
View File
@@ -1,8 +1,17 @@
#pragma once #pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <json/json.h> #include <json/json.h>
#include <sigc++/sigc++.h> #include <sigc++/sigc++.h>
#include "util/SafeSignal.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace cava { namespace cava {
@@ -21,7 +30,6 @@ extern "C" {
} // namespace cava } // namespace cava
namespace waybar::modules::cava { namespace waybar::modules::cava {
using namespace std::literals::chrono_literals;
class CavaBackend final { class CavaBackend final {
public: public:
@@ -29,19 +37,38 @@ class CavaBackend final {
virtual ~CavaBackend(); virtual ~CavaBackend();
// Methods // Methods
int getAsciiRange(); int getAsciiRange() const;
void doPauseResume(); void doPauseResume();
void Update(); void update();
const struct ::cava::config_params* getPrm(); const ::cava::config_params& getPrm() const;
std::chrono::milliseconds getFrameTimeMilsec(); 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 // Signal accessor
using type_signal_update = sigc::signal<void(const std::string&)>; using SignalUpdate = SafeSignal<const std::string&>;
type_signal_update signal_update(); SignalUpdate& signalUpdate();
using type_signal_audio_raw_update = sigc::signal<void(const ::cava::audio_raw&)>; using SignalAudioRawUpdate = SafeSignal<AudioRaw>;
type_signal_audio_raw_update signal_audio_raw_update(); SignalAudioRawUpdate& signalAudioRawUpdate();
using type_signal_silence = sigc::signal<void()>; using SignalSilence = SafeSignal<>;
type_signal_silence signal_silence(); SignalSilence& signalSilence();
using SignalConfigChanged = SafeSignal<>;
SignalConfigChanged& signalConfigChanged();
private: private:
CavaBackend(const Json::Value& config); CavaBackend(const Json::Value& config);
@@ -49,36 +76,78 @@ class CavaBackend final {
util::SleeperThread out_thread_; util::SleeperThread out_thread_;
// Cava API to read audio source // 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::error_s error_{}; // cava errors
struct ::cava::config_params prm_{}; // cava parameters 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_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data)
struct ::cava::audio_data audio_data_{}; // cava 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}; 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}; int re_paint_{0};
bool silence_{false}; bool silence_{false};
bool silence_prev_{false}; bool silence_prev_{false};
std::chrono::seconds suspend_silence_delay_{0};
int sleep_counter_{0}; int sleep_counter_{0};
std::string output_{}; std::string output_{};
// Methods // Methods
void invoke(); void invoke();
void execute(); void execute();
bool isSilence(); bool isSilent();
void doUpdate(bool force = false); void doUpdate(bool force = false);
void loadConfig(); void loadConfig();
void freeBackend(); void freeBackend();
// Signal // Signal
type_signal_update m_signal_update_; SignalUpdate m_signal_update_;
type_signal_audio_raw_update m_signal_audio_raw_; SignalAudioRawUpdate m_signal_audio_raw_;
type_signal_silence m_signal_silence_; 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 } // namespace waybar::modules::cava
+6 -4
View File
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <memory>
#ifdef HAVE_LIBCAVA #ifdef HAVE_LIBCAVA
#include "cavaRaw.hpp" #include "cavaRaw.hpp"
#include "cava_backend.hpp" #include "cava_backend.hpp"
@@ -9,16 +11,16 @@
#endif #endif
namespace waybar::modules::cava { 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 #ifdef HAVE_LIBCAVA
const std::shared_ptr<CavaBackend> backend_{waybar::modules::cava::CavaBackend::inst(config)}; const std::shared_ptr<CavaBackend> backend_{waybar::modules::cava::CavaBackend::inst(config)};
switch (backend_->getPrm()->output) { switch (backend_->getPrm().output) {
#ifdef HAVE_LIBCAVAGLSL #ifdef HAVE_LIBCAVAGLSL
case ::cava::output_method::OUTPUT_SDL_GLSL: 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 #endif
default: default:
return new waybar::modules::cava::Cava(id, config); return std::make_unique<waybar::modules::cava::CavaRaw>(id, config);
} }
#else #else
throw std::runtime_error("Unknown module"); throw std::runtime_error("Unknown module");
+147 -162
View File
@@ -6,10 +6,10 @@ waybar - cava module
# DESCRIPTION # DESCRIPTION
*cava* module for karlstav/cava project. See it on github: https://github.com/karlstav/cava. The *cava* module integrates the *karlstav/cava* audio visualizer into Waybar.
It supports two frontends: a text-based *raw* frontend and a GPU-based *GLSL*
Module supports two different frontends starting from the 0.15.0 release. The frontend that frontend. The active frontend is selected by the *method* option in the
will be used is managed by the method parameter in the [output] section of the cava configuration file. *[output]* section of the cava configuration file.
# FILES # FILES
@@ -27,185 +27,186 @@ libcava lives in:
# CONFIGURATION # CONFIGURATION
[- *Option* [- *Option*
:- *Typeof* :[ *Type*
:- *Default* :[ *Default*
:- *Description* :[ *Description*
|[ *cava_config* |[ *cava_config*
:[ string :[ string
:[ :[
:< Path where cava configuration file is placed to :[ Path to the cava configuration file. When provided, cava settings are read from it first.
|[ *method* \[output\]
:[ string
:[
:< Manages which frontend Waybar cava module should use. Values: raw, sdl_glsl. Not a waybar JSON key: it is set through the *method* option in the *\[output\]* section of the cava configuration file (*cava_config*)
|[ *framerate* |[ *framerate*
:[ integer :[ integer
:[ 30 :[ 30
:[ Frames per second. Is used as a replacement for *interval* :[ Target frames per second. Replaces the generic *interval* option.
|[ *autosens* |[ *autosens*
:[ integer :[ integer
:[ 1 :[ 1
:[ Will attempt to decrease sensitivity if the bars peak :[ Automatically decrease sensitivity when the bars peak.
|[ *sensitivity* |[ *sensitivity*
:[ integer :[ integer
:[ 100 :[ 100
:[ Manual sensitivity in %. If autosens is enabled, this will only be the initial value. 200 means double height. Accepts only non-negative values :[ Manual sensitivity in %. If *autosens* is enabled, this is only the initial value. 200 means double height. Accepts only non-negative values.
|[ *bars* |[ *bars*
:[ integer :[ integer
:[ 12 :[ 12
:[ The number of bars :[ The number of bars.
|[ *lower_cutoff_freq* |[ *lower_cutoff_freq*
:[ long integer :[ long integer
:[ 50 :[ 50
:[ Lower cutoff frequencies for lowest bars the bandwidth of the visualizer :[ Lower cutoff frequency for the visualizer bandwidth.
|[ *higher_cutoff_freq* |[ *higher_cutoff_freq*
:[ long integer :[ long integer
:[ 10000 :[ 10000
:[ Higher cutoff frequencies for highest bars the bandwidth of the visualizer :[ Higher cutoff frequency for the visualizer bandwidth.
|[ *sleep_timer* |[ *sleep_timer*
:[ integer :[ integer
:[ 5 :[ 5
:[ Seconds with no input before cava main thread goes to sleep mode :[ Seconds of silence before cava enters sleep mode.
|[ *hide_on_silence* |[ *hide_on_silence*
:[ bool :[ bool
:[ false :[ false
:[ Hides the widget if no input (after sleep_timer elapsed) :[ Hide the widget when silence lasts longer than *sleep_timer*.
|[ *format_silent* |[ *format_silent*
:[ string :[ string
:[ :[
:[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false) :[ Text shown when the module is silent and *hide_on_silence* is false. **Raw frontend only.**
|[ *format-icons*
:[ array
:[
:[ Array of characters used to render bar levels in the raw frontend. The number of items determines the dynamic range.
|[ *method* \[input\] |[ *method* \[input\]
:[ string :[ string
:[ pulse :[ pulse
:[ Audio capturing method. Possible methods are: pipewire, pulse, alsa, fifo, sndio or shmem :[ Audio capture backend. Supported values: pipewire, pulse, alsa, fifo, sndio, shmem.
|[ *source* |[ *source*
:[ string :[ string
:[ auto :[ auto
:[ See cava configuration :[ Audio source identifier. See the cava documentation for details.
|[ *sample_rate* |[ *sample_rate*
:[ long integer :[ long integer
:[ 44100 :[ 44100
:[ See cava configuration :[ See the cava documentation.
|[ *sample_bits* |[ *sample_bits*
:[ integer :[ integer
:[ 16 :[ 16
:[ See cava configuration :[ See the cava documentation.
|[ *stereo* |[ *stereo*
:[ bool :[ bool
:[ true :[ true
:[ Visual channels :[ Enable stereo visualization.
|[ *reverse* |[ *reverse*
:[ bool :[ bool
:[ false :[ false
:[ Displays frequencies the other way around :[ Reverse the bar order (highest frequencies on the left).
|[ *bar_delimiter* |[ *bar_delimiter*
:[ integer :[ integer
:[ 0 :[ 0
:[ Each bar is separated by a delimiter. Use decimal value in ascii table(i.e. 59 = ";"). 0 means no delimiter :[ Delimiter placed between bars in the raw output. Use a decimal ASCII value (e.g. 59 = ";"). 0 means no delimiter.
|[ *monstercat* |[ *monstercat*
:[ bool :[ bool
:[ false :[ false
:[ Disables or enables the so-called "Monstercat smoothing" with or without "waves" :[ Enable Monstercat smoothing.
|[ *waves* |[ *waves*
:[ bool :[ bool
:[ false :[ false
:[ Disables or enables the so-called "Monstercat smoothing" with or without "waves" :[ Enable the waves effect alongside Monstercat smoothing.
|[ *noise_reduction* |[ *noise_reduction*
:[ double :[ double
:[ 0.77 :[ 0.77
:[ Fractional value between 0.0 - 1.0. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. Values near 1.0 will be very slow and smooth, near 0.0 will be fast but noisy :[ Smoothing factor between 0.0 and 1.0. Higher values produce slower, smoother animation; lower values are more reactive but noisy.
|[ *gravity* |[ *gravity*
:[ integer :[ integer
:[ :[
:[ Gravity smoothing filter. Higher values make the bars drop faster. Adjusted by *noise_reduction* when set :[ Gravity factor. Higher values make bars fall faster. When *noise_reduction* is set, this is derived automatically.
|[ *integral* |[ *integral*
:[ integer :[ integer
:[ :[
:[ Integral smoothing filter. Higher values make the visualization smoother but less precise. Adjusted by *noise_reduction* when set :[ Integral smoothing factor. Higher values produce smoother but less precise animation. When *noise_reduction* is set, this is derived automatically.
|[ *input_delay* |[ *input_delay*
:[ integer :[ integer
:[ 4 :[ 4
:[ Sets the delay before fetching audio source thread start working. On author's machine, Waybar starts much faster than pipewire audio server, and without a little delay cava module fails because pipewire is not ready :[ Delay in seconds before starting audio capture. Increase this if Waybar starts before the audio server (e.g. PipeWire).
|[ *ascii_max_range*
:[ integer
:[ 7
:[ It's impossible to set it directly. The value is dictated by the number of icons in the array *format-icons*
|[ *data_format*
:[ string
:[ ascii
:[ Raw data format. Can be 'binary' or 'ascii'
|[ *raw_target*
:[ string
:[ /dev/stdout
:[ Raw output target. A fifo will be created if target does not exist
|[ *menu*
:[ string
:[
:[ Action that popups the menu.
|[ *menu-file*
:[ string
:[
:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*
|[ *menu-actions*
:[ array
:[
:[ The actions corresponding to the buttons of the menu.
|[ *bar_spacing*
:[ integer
:[
:[ Bars' space between bars in number of characters
|[ *bar_width* |[ *bar_width*
:[ integer :[ integer
:[ :[
:[ Bars' width between bars in number of characters :[ Bar width in pixels. **Used by the GLSL frontend.**
|[ *bar_spacing*
:[ integer
:[
:[ Space between bars in pixels. **Used by the GLSL frontend.**
|[ *bar_height* |[ *bar_height*
:[ integer :[ integer
:[ :[
:[ Useless. bar_height is only used for output in "noritake" format :[ Ignored by Waybar. Used only by cava's "noritake" output format.
|[ *background* |[ *menu*
:[ string :[ string
:[ :[
:[ GLSL actual. Support hex code colors only. Must be within ''. Not a waybar JSON key: set it as *background* in the *\[color\]* section of the cava configuration file (*cava_config*) :[ Action that opens the menu.
|[ *foreground* |[ *menu-file*
:[ string :[ string
:[ :[
:[ GLSL actual. Support hex code colors only. Must be within ''. Not a waybar JSON key: set it as *foreground* in the *\[color\]* section of the cava configuration file (*cava_config*) :[ Location of the menu descriptor file. There must be a GtkMenu element with id *menu*.
|[ *gradient* |[ *menu-actions*
:[ integer :[ array
:[ 0
:[ GLSL actual. Gradient mode(0/1 - on/off)
|[ *gradient_count*
:[ integer
:[ 0
:[ GLSL actual. The count of colors for the gradient
|[ *gradient_color_N*
:[ string
:[ :[
:[ GLSL actual. N - the number of the gradient color between 1 and 8. Only hex defined colors are supported. Must be within '' :[ Actions corresponding to the buttons of the menu.
|[ *method* \[output\]
:[ string
:[ raw
:[ Cava output method. Set to *raw* for the text frontend or *sdl_glsl* for the GPU frontend. **This is set inside the *[output]* section of the cava configuration file, not in Waybar's JSON.**
|[ *sdl_width* |[ *sdl_width*
:[ integer :[ integer
:[ :[
:[ GLSL actual. Manages the width of the waybar cava GLSL frontend module :[ GLSL frontend width in pixels. **GLSL only.**
|[ *sdl_height* |[ *sdl_height*
:[ integer :[ integer
:[ :[
:[ GLSL actual. Manages the height of the waybar cava GLSL frontend module :[ GLSL frontend height in pixels. **GLSL only.**
|[ *vertex_shader*
:[ string
:[
:[ Path to the vertex shader. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *fragment_shader*
:[ string
:[
:[ Path to the fragment shader. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *continuous_rendering* |[ *continuous_rendering*
:[ integer :[ integer
:[ 0 :[ 0
:[ GLSL actual. Keep rendering even if no audio. Recommended to set to 1. Not a waybar JSON key: set it as *continuous_rendering* in the *\[output\]* section of the cava configuration file (*cava_config*) :[ Continue rendering when silent. Set to 1 for smooth animation. **GLSL only; set in the *[output]* section of the cava configuration file.**
|[ *background*
:[ string
:[
:[ Background color as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *foreground*
:[ string
:[
:[ Foreground color as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *gradient*
:[ integer
:[ 0
:[ Enable gradient mode (0 = off, 1 = on). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *gradient_count*
:[ integer
:[ 0
:[ Number of gradient colors (up to 8). **GLSL only; set in the *[color]* section of the cava configuration file.**
|[ *gradient_color_N*
:[ string
:[
:[ Gradient color N (18) as a '#RRGGBB' hex string (must be quoted). **GLSL only; set in the *[color]* section of the cava configuration file.**
Configuration can be provided as: Configuration can be provided in three ways:
- The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped
- Without cava configuration file. In such case cava should be configured through provided list of the configuration option - **Cava config only**: set *cava_config* to a cava configuration file and omit all other options.
- Mix. When provided both And cava configuration file And configuration options. In such case, waybar applies configuration file first and then overrides particular options by the provided list of configuration options - **Waybar JSON only**: leave out *cava_config* and set every option in Waybar's module configuration.
- **Mixed**: provide a *cava_config* and also set specific options in Waybar's JSON. Waybar reads the file first, then overrides any values present in the JSON.
# ACTIONS # ACTIONS
[- *String* [- *String*
:- *Action* :[ *Action*
|[ *mode* |[ *mode*
:< Switch main cava thread and fetch audio source thread from/to pause/resume :[ Toggle pause/resume for the audio capture and output threads.
# DEPENDENCIES # DEPENDENCIES
@@ -215,31 +216,32 @@ Configuration can be provided as:
# SOLVING ISSUES # SOLVING ISSUES
. On start Waybar throws an exception "error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory". . At startup Waybar fails with *"error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory"*.
It might happen when libcava for some reason hasn't been registered in the system. sudo ldconfig should help. This happens when libcava has not been registered in the system library cache. Run *sudo ldconfig* to refresh the cache.
It might also happen when Waybar was installed into /usr/local while libcava lives elsewhere. In that case: This can also occur when Waybar is installed under */usr/local* but libcava is elsewhere. To fix it:
1. Drop the local cava library: sudo rm -rfv /usr/local/include/cava /usr/local/lib64/pkgconfig/cava.pc /usr/local/lib64/libcava.so 1. Remove the local libcava installation: *sudo rm -rfv /usr/local/include/cava /usr/local/lib64/pkgconfig/cava.pc /usr/local/lib64/libcava.so*
2. Set the prefix where Waybar should be installed: meson configure build -Dprefix="/usr" 2. Reconfigure Waybar to use the system prefix: *meson configure build -Dprefix="/usr"*
3. Build Waybar: make 3. Rebuild Waybar: *ninja -C build*
4. Install Waybar into the system: sudo meson install -C build 4. Install Waybar: *sudo meson install -C build*
. Waybar is starting but cava module doesn't react to the music
1. In such cases at first need to make sure usual cava application is working as well
2. If so, need to comment all configuration options. Uncomment cava_config and provide the path to the working cava config
3. You might set too huge or too small input_delay. Try to setup to 4 seconds, restart waybar, and check again 4 seconds past. Usual even on weak machines it should be enough
4. You might accidentally switch action mode to pause mode
# RISING ISSUES . Waybar starts but the cava module does not react to audio.
1. First, verify that standalone cava works correctly.
2. If it does, comment out all Waybar cava options, uncomment *cava_config*, and point it to the working cava configuration file.
3. The *input_delay* may be too large or too small. Try setting it to 4 seconds, restart Waybar, and check again after that delay. This is usually sufficient, even on slower machines.
4. You may have accidentally toggled pause mode via an action.
For clear understanding: this module is a cava API's consumer. So for any bugs related to cava engine you should contact Cava upstream(https://github.com/karlstav/cava) ++ # REPORTING ISSUES
with the one Exception. Cava upstream doesn't provide cava as a shared library. For that, this module author made a fork libcava(https://github.com/LukashonakV/cava). ++
So the order is: This module is a consumer of the cava API. For bugs in the cava engine itself, please report them to [cava upstream](https://github.com/karlstav/cava) first.
. cava upstream
. libcava upstream. Upstream cava does not provide a shared library. The Waybar cava module uses [libcava](https://github.com/LukashonakV/cava), a fork maintained by the module author, to provide one. If the issue is specific to the shared library packaging, report it to libcava.
In case when cava releases new version and you're wanna get it, it should be raised an issue to libcava(https://github.com/LukashonakV/cava) with title ++
\[Bump\]x.x.x where x.x.x is cava release version. When requesting a new upstream cava release to be packaged in libcava, open an issue at libcava with the title `[Bump] x.x.x`, where `x.x.x` is the desired cava version.
# EXAMPLES # EXAMPLES
## Raw frontend
``` ```
"cava": { "cava": {
//"cava_config": "$XDG_CONFIG_HOME/cava/cava.conf", //"cava_config": "$XDG_CONFIG_HOME/cava/cava.conf",
@@ -253,7 +255,6 @@ In case when cava releases new version and you're wanna get it, it should be rai
"source": "auto", "source": "auto",
"stereo": true, "stereo": true,
"reverse": false, "reverse": false,
"bar_delimiter": 0,
"monstercat": false, "monstercat": false,
"waves": false, "waves": false,
"noise_reduction": 0.77, "noise_reduction": 0.77,
@@ -264,39 +265,6 @@ In case when cava releases new version and you're wanna get it, it should be rai
} }
}, },
``` ```
# STYLE
- *#cava* Raw frontend widget
- *#cava.silent* Applied after no sound has been detected for sleep_timer seconds
- *#cava.updated* Applied when a new frame is shown
- *#cavaGLSL* GLSL frontend widget (used instead of *#cava* when the cava *method* is *sdl_glsl*)
- *#cavaGLSL.silent* Applied after no sound has been detected for sleep_timer seconds
- *#cavaGLSL.updated* Applied when a new frame is shown
# FRONTENDS
## RAW
The cava raw frontend uses ASCII characters to visualize incoming audio data. Each ASCII symbol position corresponds to the value of the audio power pulse.
Under the hood:
```
. Incoming audio power pulse list is : 12684
. Configured array of ASCII codes is: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ]. See `format-icons` https://github.com/Alexays/Waybar/wiki/Module:-Cava#example
```
As a result cava frontend will give ▁▂▆█▄
Examples:
waybar config
```
"cava": {
"cava_config": "$XDG_CONFIG_HOME/cava/waybar_raw.conf",
"input_delay": 2,
"format-icons" : ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ],
"actions": {
"on-click-right": "mode"
}
},
```
waybar_raw.conf waybar_raw.conf
``` ```
@@ -367,6 +335,7 @@ sleep_timer = 5
# README.md contains further information on how to setup CAVA for JACK. # README.md contains further information on how to setup CAVA for JACK.
# #
# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods: # The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
# sample_rate: fifo, pipewire, sndio, oss # sample_rate: fifo, pipewire, sndio, oss
# sample_bits: fifo, pipewire, sndio, oss # sample_bits: fifo, pipewire, sndio, oss
@@ -468,7 +437,7 @@ bar_delimiter = 0
# Noise reduction, int 0 - 100. default 77 # Noise reduction, int 0 - 100. default 77
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth # the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
# 100 will be very slow and smooth, 0 will be fast but noisy. # 100 will be very slow and smooth, 0 will be fast and noisy.
[eq] [eq]
@@ -477,33 +446,32 @@ bar_delimiter = 0
# Remember to uncomment more than one key! More keys = more precision. # Remember to uncomment more than one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples. # Look at readme.md on github for further explanations and examples.
``` ```
## GLSL
The Cava GLSL frontend delegates the visualization of incoming audio data to the GPU via OpenGL.
There are some mandatory dependencies that need to be satisfied in order for Cava GLSL to be built and function properly: ## GLSL frontend
. epoxy library must be installed on the system The GLSL frontend requires:
. Vertex and fragment shaders from the original project must be used. They should be downloaded, and the file paths must be configured correctly in the Waybar Cava configuration:
1. cava shaders [cava shaders](https://github.com/karlstav/cava/tree/master/output/shaders)
2. libcava shaders [libcava shaders](https://github.com/LukashonakV/cava/tree/master/output/shaders)
. It is highly recommended to have a separate cava configuration for the Waybar Cava GLSL module and to use this as the cava_config in the Waybar configuration.
. It is common for cava configurations to be placed in the XDG_CONFIG_HOME directory, including shaders as well. Consider keeping them in the $XDG_CONFIG_HOME/cava/shaders folder.
Key configuration options: . The *epoxy* library.
. Vertex and fragment shaders from the cava project. Download them and place under _$XDG_CONFIG_HOME/cava/shaders_, then reference them in the cava configuration:
1. [cava shaders](https://github.com/karlstav/cava/tree/master/output/shaders)
2. [libcava shaders](https://github.com/LukashonakV/cava/tree/master/output/shaders)
. A separate cava configuration file is highly recommended.
. bars. The more values the parameter has, the more interesting the visualization becomes. Key cava configuration options for GLSL:
. method in output section must be set to sdl_glsl
. sdl_width and sdl_height manage the size of the module. Adjust them according to your needs. . *bars* — higher values produce more detailed visualization.
. Shaders for sdl_glsl, located in $HOME/.config/cava/shaders. Example: "vertex_shader" = "pass_through.vert" "fragment_shader" = "spectrogram.frag" . *method* in *[output]* must be set to *sdl_glsl*.
. Set continuous_rendering to 1 to enable smooth rendering; set it to 0 otherwise. It is recommended to keep it set to 1. . *sdl_width* and *sdl_height* control the module size.
. background, foreground, and gradient_color_N (where N is a number between 1 and 8) must be defined using hex code . *vertex_shader* and *fragment_shader* point to the shader files under _$HOME/.config/cava/shaders_.
. *continuous_rendering* — set to 1 for smooth animation.
. *background*, *foreground*, and *gradient_color_N* must use hex codes inside single quotes.
Example: Example:
waybar config waybar config
``` ```
"cava": { "cava": {
"cava_config": "$XDG_CONFIG_HOME/cava/waybar_cava#3.conf", "cava_config": "$XDG_CONFIG_HOME/cava/waybar_cava.conf",
"input_delay": 2, "input_delay": 2,
"actions": { "actions": {
"on-click-right": "mode" "on-click-right": "mode"
@@ -511,7 +479,7 @@ waybar config
}, },
``` ```
waybar_raw.conf waybar_cava.conf
``` ```
## Configuration file for CAVA. ## Configuration file for CAVA.
# Remove the ; to change parameters. # Remove the ; to change parameters.
@@ -721,4 +689,21 @@ gradient_color_2 = '#45475A'
# Look at readme.md on github for further explanations and examples. # Look at readme.md on github for further explanations and examples.
``` ```
Different waybar_cava#N.conf see at [cava GLSL](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL) More GLSL examples are available on the [cava GLSL wiki page](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL).
# STYLE
- *#cava* Raw frontend widget
- *#cava.silent* Applied after no sound has been detected for *sleep_timer* seconds
- *#cava.updated* Applied when a new frame is shown
- *#cavaGLSL* GLSL frontend widget (used instead of *#cava* when the cava *method* is *sdl_glsl*)
- *#cavaGLSL.silent* Applied after no sound has been detected for *sleep_timer* seconds
- *#cavaGLSL.updated* Applied when a new frame is shown
# FRONTENDS
## RAW
The raw frontend maps each bar's amplitude to a character from *format-icons*. The final widget text is the concatenation of all characters, optionally separated by *bar_delimiter*. See the EXAMPLES section above for a complete configuration.
## GLSL
The GLSL frontend renders the visualization with OpenGL ES using user-provided shaders. It is selected by setting *method = sdl_glsl* in the cava configuration. See the EXAMPLES section above for a complete configuration.
+1 -1
View File
@@ -372,7 +372,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
} }
#endif #endif
if (ref == "cava") { if (ref == "cava") {
return waybar::modules::cava::getModule(id, config_[name]); return waybar::modules::cava::getModule(id, config_[name]).release();
} }
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
if (ref == "systemd-failed-units") { if (ref == "systemd-failed-units") {
+259 -104
View File
@@ -2,88 +2,214 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream> #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) waybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config)
: AModule(config, "cavaGLSL", id, false, false), : AModule(config, "cavaGLSL", id, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} { 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 (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (!id.empty()) { 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); gl_area_.set_use_es(true);
// set_auto_render(true); gl_area_.signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize));
signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize)); gl_area_.signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false);
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 cacheConfigParams(backend_->getPrm());
prm_ = *backend_->getPrm();
// Set widget length
int length{0}; int length{0};
if (config_["min-length"].isUInt()) if (config_["min-length"].isUInt())
length = config_["min-length"].asUInt(); length = config_["min-length"].asUInt();
else if (config_["max-length"].isUInt()) else if (config_["max-length"].isUInt())
length = config_["max-length"].asUInt(); length = config_["max-length"].asUInt();
else 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 audio_raw_update_conn_ =
backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate)); backend_->signalAudioRawUpdate().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate));
// Subscribe for silence silence_conn_ = backend_->signalSilence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence));
backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence)); config_changed_conn_ =
event_box_.add(*this); 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 { waybar::modules::cava::CavaGLSL::~CavaGLSL() {
Glib::signal_idle().connect_once([this, input]() { audio_raw_update_conn_.disconnect();
m_data_ = std::make_shared<::cava::audio_raw>(input); silence_conn_.disconnect();
if (silence_) { config_changed_conn_.disconnect();
get_style_context()->remove_class("silent");
if (!get_style_context()->has_class("updated")) get_style_context()->add_class("updated");
show();
silence_ = false;
}
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 { auto waybar::modules::cava::CavaGLSL::onSilence() -> void {
Glib::signal_idle().connect_once([this]() { if (!silence_) {
if (!silence_) { if (gl_area_.get_style_context()->has_class("updated"))
if (get_style_context()->has_class("updated")) get_style_context()->remove_class("updated"); gl_area_.get_style_context()->remove_class("updated");
if (hide_on_silence_) hide(); if (hide_on_silence_) gl_area_.hide();
silence_ = true; silence_ = true;
get_style_context()->add_class("silent"); gl_area_.get_style_context()->add_class("silent");
// Set clear color to black }
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); }
queue_render();
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) { 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); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_); 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_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); glUniform1fv(uniform_previous_bars_, m_data_.number_of_bars, m_data_.previous_bars_raw.data());
glUniform1i(uniform_bars_count_, m_data_->number_of_bars); glUniform1i(uniform_bars_count_, m_data_.number_of_bars);
++frame_counter; ++frame_counter_;
glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3); 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); glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_); 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() { void waybar::modules::cava::CavaGLSL::onRealize() {
make_current(); gl_area_.make_current();
cleanupGL();
initShaders(); initShaders();
if (shaderProgram_ == 0) {
return;
}
initGLSL(); initGLSL();
initSurface(); initSurface();
} }
struct colors { struct Colors {
uint16_t R; uint16_t R;
uint16_t G; uint16_t G;
uint16_t B; uint16_t B;
}; };
static void parse_color(char* color_string, struct colors* color) { static void parse_color(const char* color_string, struct Colors* color) {
if (color_string[0] == '#') { if (color_string == nullptr) {
sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B); 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}; GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
GLint indexData[]{0, 1, 2, 3}; GLint indexData[]{0, 1, 2, 3};
GLuint gVBO{0}; glGenBuffers(1, &vbo_);
glGenBuffers(1, &gVBO); glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);
GLuint gIBO{0}; glGenBuffers(1, &ibo_);
glGenBuffers(1, &gIBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);
GLuint gVAO{0}; glGenVertexArrays(1, &vao_);
glGenVertexArrays(1, &gVAO); glBindVertexArray(vao_);
glBindVertexArray(gVAO);
glEnableVertexAttribArray(gVertexPos2DLocation); glEnableVertexAttribArray(gVertexPos2DLocation);
glBindBuffer(GL_ARRAY_BUFFER, gVBO); glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr); 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_); glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_); glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create a texture to attach the framebuffer
glGenTextures(1, &texture_); glGenTextures(1, &texture_);
glBindTexture(GL_TEXTURE_2D, 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); GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0);
// Check is framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
spdlog::error("{0}. Framebuffer not complete", name_); spdlog::error("{0}. Framebuffer not complete", name_);
} }
// Unbind the framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars"); uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars");
uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars"); uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars");
uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count"); uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count");
uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time"); uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time");
uniform_input_texture_ = glGetUniformLocation(shaderProgram_, "inputTexture");
GLuint err{glGetError()}; GLuint err{glGetError()};
if (err != 0) { if (err != 0) {
@@ -174,32 +309,32 @@ void waybar::modules::cava::CavaGLSL::initGLSL() {
} }
void waybar::modules::cava::CavaGLSL::initSurface() { void waybar::modules::cava::CavaGLSL::initSurface() {
colors color = {0}; Colors color = {0};
GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")}; GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")};
parse_color(prm_.bcolor, &color); parse_color(bcolor_.c_str(), &color);
glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0, glUniform3f(uniform_bg_col, static_cast<float>(color.R) / 255.0f, static_cast<float>(color.G) / 255.0f,
(float)color.B / 255.0); static_cast<float>(color.B) / 255.0f);
GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")}; GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")};
parse_color(prm_.color, &color); parse_color(color_.c_str(), &color);
glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0, glUniform3f(uniform_fg_col, static_cast<float>(color.R) / 255.0f, static_cast<float>(color.G) / 255.0f,
(float)color.B / 255.0); static_cast<float>(color.B) / 255.0f);
GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")}; 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")}; 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")}; 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")}; 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")}; GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, "gradient_colors")};
GLfloat gradient_colors[8][3]; GLfloat gradient_colors[8][3] = {};
for (int i{0}; i < prm_.gradient_count; ++i) { for (int i{0}; i < gradient_count_; ++i) {
parse_color(prm_.gradient_colors[i], &color); parse_color(gradient_colors_[i].c_str(), &color);
gradient_colors[i][0] = (float)color.R / 255.0; gradient_colors[i][0] = static_cast<float>(color.R) / 255.0f;
gradient_colors[i][1] = (float)color.G / 255.0; gradient_colors[i][1] = static_cast<float>(color.G) / 255.0f;
gradient_colors[i][2] = (float)color.B / 255.0; 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); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); 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() { void waybar::modules::cava::CavaGLSL::initShaders() {
shaderProgram_ = glCreateProgram(); 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 vertexShader{loadShader(vertex_shader_, GL_VERTEX_SHADER)};
GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_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_, vertexShader);
glAttachShader(shaderProgram_, fragmentShader); glAttachShader(shaderProgram_, fragmentShader);
@@ -219,14 +368,18 @@ void waybar::modules::cava::CavaGLSL::initShaders() {
glDeleteShader(vertexShader); glDeleteShader(vertexShader);
glDeleteShader(fragmentShader); glDeleteShader(fragmentShader);
// Check for linking errors GLint success{0};
GLint success, len;
glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success); glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);
if (!success) { if (!success) {
GLint len{0};
glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len); glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len);
GLchar* infoLog{(char*)'\0'}; std::vector<GLchar> infoLog(len + 1);
glGetProgramInfoLog(shaderProgram_, len, &len, infoLog); glGetProgramInfoLog(shaderProgram_, len, nullptr, infoLog.data());
spdlog::error("{0}. Shader linking error: {1}", name_, infoLog); spdlog::error("{0}. Shader linking error: {1}", name_, infoLog.data());
glDeleteProgram(shaderProgram_);
shaderProgram_ = 0;
gl_area_.hide();
return;
} }
glReleaseShaderCompiler(); glReleaseShaderCompiler();
@@ -236,35 +389,37 @@ void waybar::modules::cava::CavaGLSL::initShaders() {
GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) { GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) {
spdlog::debug("{0}. loadShader: {1}", name_, fileName); spdlog::debug("{0}. loadShader: {1}", name_, fileName);
// Read shader source code from the file
std::ifstream shaderFile{fileName}; std::ifstream shaderFile{fileName};
if (!shaderFile.is_open()) { if (!shaderFile.is_open()) {
spdlog::error("{0}. Could not open shader file: {1}", name_, fileName); spdlog::error("{0}. Could not open shader file: {1}", name_, fileName);
return 0;
} }
std::ostringstream buffer; std::ostringstream buffer;
buffer << shaderFile.rdbuf(); // read file content into stringstream buffer << shaderFile.rdbuf();
std::string str{buffer.str()}; std::string str{buffer.str()};
const char* shaderSource = str.c_str();
shaderFile.close(); shaderFile.close();
GLuint shaderID{glCreateShader(type)}; 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); glShaderSource(shaderID, 1, &shaderSource, nullptr);
glCompileShader(shaderID); glCompileShader(shaderID);
// Check for compilation errors GLint success{0};
GLint success, len;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success); glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
if (!success) { if (!success) {
GLint len{0};
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len); glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len);
std::vector<GLchar> infoLog(len + 1);
GLchar* infoLog{(char*)'\0'}; glGetShaderInfoLog(shaderID, len, nullptr, infoLog.data());
glGetShaderInfoLog(shaderID, len, nullptr, infoLog); spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog.data());
spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog); glDeleteShader(shaderID);
return 0;
} }
return shaderID; return shaderID;
+48 -39
View File
@@ -2,59 +2,68 @@
#include <spdlog/spdlog.h> #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), : ALabel(config, "cava", id, "{}", 60, false, false, false),
backend_{waybar::modules::cava::CavaBackend::inst(config)} { backend_{waybar::modules::cava::CavaBackend::inst(config)} {
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); 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(); if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString();
ascii_range_ = backend_->getAsciiRange(); update_conn_ = backend_->signalUpdate().connect(sigc::mem_fun(*this, &CavaRaw::onUpdate));
backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate)); silence_conn_ = backend_->signalSilence().connect(sigc::mem_fun(*this, &CavaRaw::onSilence));
backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence)); backend_->update();
backend_->Update();
} }
auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void { waybar::modules::cava::CavaRaw::~CavaRaw() {
if ((actionMap_[name])) { update_conn_.disconnect();
(this->*actionMap_[name])(); silence_conn_.disconnect();
} else }
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); spdlog::error("Cava. Unsupported action \"{0}\"", name);
}
} }
// Cava actions // Cava actions
void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } void waybar::modules::cava::CavaRaw::pauseResume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { auto waybar::modules::cava::CavaRaw::onUpdate(const std::string& input) -> void {
Glib::signal_idle().connect_once([this, input]() { if (silence_) {
if (silence_) { silence_ = false;
silence_ = false; label_.get_style_context()->remove_class("silent");
label_.get_style_context()->remove_class("silent"); if (!label_.get_style_context()->has_class("updated"))
if (!label_.get_style_context()->has_class("updated")) label_.get_style_context()->add_class("updated");
label_.get_style_context()->add_class("updated"); }
} label_text_.clear();
label_text_.clear(); auto ascii_range = backend_->getAsciiRange();
for (auto& ch : input) for (auto& ch : input) {
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1)); 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_.set_markup(label_text_);
label_.show(); label_.show();
ALabel::update(); ALabel::update();
});
} }
auto waybar::modules::cava::Cava::onSilence() -> void { auto waybar::modules::cava::CavaRaw::onSilence() -> void {
Glib::signal_idle().connect_once([this]() { if (!silence_) {
if (!silence_) { if (label_.get_style_context()->has_class("updated"))
if (label_.get_style_context()->has_class("updated")) label_.get_style_context()->remove_class("updated");
label_.get_style_context()->remove_class("updated");
if (hide_on_silence_) { if (hide_on_silence_) {
// Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout // Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout
label_.set_markup(""); label_.set_markup("");
label_.hide(); label_.hide();
} else if (config_["format_silent"].isString()) } else if (!format_silent_.empty()) {
label_.set_markup(format_silent_); label_.set_markup(format_silent_);
silence_ = true;
label_.get_style_context()->add_class("silent");
} }
}); silence_ = true;
label_.get_style_context()->add_class("silent");
}
} }
+225 -151
View File
@@ -2,8 +2,23 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <stdexcept> #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( std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(
const Json::Value& config) { const Json::Value& config) {
static auto* backend = new CavaBackend(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) { waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) {
// Load waybar module config
loadConfig(); loadConfig();
// Read audio source trough cava API. Cava orginizes this process via infinity loop
read_thread_ = [this] { read_thread_ = [this] {
try { while (read_thread_.isRunning()) {
input_source_(&audio_data_); try {
} catch (const std::runtime_error& e) { if (input_source_) {
spdlog::warn("Cava backend. Read source error: {0}", e.what()); 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 std::lock_guard<std::mutex> lk(read_thread_exit_mutex_);
// logs instead of terminating the process (#4456). read_thread_exited_ = true;
try {
loadConfig();
} catch (const std::exception& e) {
spdlog::error("{}", e.what());
} }
read_thread_exit_cv_.notify_one();
}; };
// Write outcoming data. Emit signals
out_thread_ = [this] { out_thread_ = [this] {
doUpdate(false); try {
out_thread_.sleep_for(frame_time_milsec_); 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() { 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(); out_thread_.stop();
read_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(); freeBackend();
} }
static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { bool waybar::modules::cava::CavaBackend::isSilent() {
if (delta == std::chrono::seconds{0}) { pthread_mutex_lock(&audio_data_.lock);
delta += std::chrono::seconds{1}; bool silent = true;
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() {
for (int i{0}; i < audio_data_.input_buffer_size; ++i) { for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
if (audio_data_.cava_in[i]) { if (audio_data_.cava_in[i]) {
return false; silent = false;
break;
} }
} }
pthread_mutex_unlock(&audio_data_.lock);
return true; 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() { void waybar::modules::cava::CavaBackend::invoke() {
pthread_mutex_lock(&audio_data_.lock); pthread_mutex_lock(&audio_data_.lock);
::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, ::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); pthread_mutex_unlock(&audio_data_.lock);
} }
// Do transformation under raw data
void waybar::modules::cava::CavaBackend::execute() { void waybar::modules::cava::CavaBackend::execute() {
invoke(); invoke();
audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_); 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() { void waybar::modules::cava::CavaBackend::doPauseResume() {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
pthread_mutex_lock(&audio_data_.lock); pthread_mutex_lock(&audio_data_.lock);
if (audio_data_.suspendFlag) { if (audio_data_.suspendFlag) {
audio_data_.suspendFlag = false; audio_data_.suspendFlag = false;
pthread_cond_broadcast(&audio_data_.resumeCond); pthread_cond_broadcast(&audio_data_.resumeCond);
downThreadDelay(frame_time_milsec_, suspend_silence_delay_); adaptive_delay_.decrease();
} else { } else {
audio_data_.suspendFlag = true; audio_data_.suspendFlag = true;
upThreadDelay(frame_time_milsec_, suspend_silence_delay_); adaptive_delay_.increase();
} }
pthread_mutex_unlock(&audio_data_.lock); pthread_mutex_unlock(&audio_data_.lock);
Update(); update();
} }
waybar::modules::cava::CavaBackend::type_signal_update waybar::modules::cava::CavaBackend::SignalUpdate&
waybar::modules::cava::CavaBackend::signal_update() { waybar::modules::cava::CavaBackend::signalUpdate() {
return m_signal_update_; return m_signal_update_;
} }
waybar::modules::cava::CavaBackend::type_signal_audio_raw_update waybar::modules::cava::CavaBackend::SignalAudioRawUpdate&
waybar::modules::cava::CavaBackend::signal_audio_raw_update() { waybar::modules::cava::CavaBackend::signalAudioRawUpdate() {
return m_signal_audio_raw_; return m_signal_audio_raw_;
} }
waybar::modules::cava::CavaBackend::type_signal_silence waybar::modules::cava::CavaBackend::SignalSilence&
waybar::modules::cava::CavaBackend::signal_silence() { waybar::modules::cava::CavaBackend::signalSilence() {
return m_signal_silence_; 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) { 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; if (audio_data_.suspendFlag && !force) return;
silence_ = isSilence(); silence_ = isSilent();
if (!silence_) sleep_counter_ = 0; if (!silence_) sleep_counter_ = 0;
if (silence_ && prm_.sleep_timer != 0) { if (silence_ && prm_.sleep_timer != 0) {
if (sleep_counter_ <= 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_; ++sleep_counter_;
silence_ = false; silence_ = false;
} }
} }
if (!silence_ || prm_.sleep_timer == 0) { if (!silence_ || prm_.sleep_timer == 0) {
if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); while (adaptive_delay_.decrease()) {}
execute(); execute();
if (re_paint_ == 1 || force || prm_.continuous_rendering) { if (re_paint_ == 1 || force || prm_.continuous_rendering) {
m_signal_update_.emit(output_); m_signal_update_.emit(output_);
m_signal_audio_raw_.emit(audio_raw_); m_signal_audio_raw_.emit(AudioRaw{audio_raw_});
} }
} else { } else {
if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); while (adaptive_delay_.increase()) {}
if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit();
} }
silence_prev_ = silence_; silence_prev_ = silence_;
} }
void waybar::modules::cava::CavaBackend::freeBackend() { void waybar::modules::cava::CavaBackend::freeBackend() {
if (plan_ != NULL) { input_source_ = nullptr;
if (plan_ != nullptr) {
cava_destroy(plan_); 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); pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1; audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock); pthread_mutex_unlock(&audio_data_.lock);
free_config(&prm_); free_config(&prm_);
prm_ = {};
free(audio_data_.source); free(audio_data_.source);
audio_data_.source = nullptr;
free(audio_data_.cava_in); free(audio_data_.cava_in);
audio_data_.cava_in = nullptr;
} }
void waybar::modules::cava::CavaBackend::loadConfig() { void waybar::modules::cava::CavaBackend::loadConfig() {
freeBackend(); std::lock_guard<std::recursive_mutex> lock(state_mutex_);
// Load waybar module config if (shutdown_.load()) {
char cfgPath[PATH_MAX]; return;
cfgPath[0] = '\0'; }
if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data()); const Json::Value& cfg = config_;
// Load cava 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; error_.length = 0;
if (!load_config(cfgPath, &prm_, &error_)) { if (!load_config(cfgPath.data(), &new_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_.
throw std::runtime_error(std::string{"cava backend: error loading config: "} + error_.message); throw std::runtime_error(std::string{"cava backend: error loading config: "} + error_.message);
} }
// Override cava parameters by the user config new_prm.inAtty = 0;
prm_.inAtty = 0; auto const output{new_prm.output};
auto const output{prm_.output}; if (new_prm.data_format) free(new_prm.data_format);
// prm_.output = ::cava::output_method::OUTPUT_RAW; new_prm.data_format = strdup(
if (prm_.data_format) free(prm_.data_format); cfg["data_format"].isString() ? cfg["data_format"].asString().c_str() : "ascii");
// Default to ascii for format-icons output; allow user override if (cfg["raw_target"].isString()) {
prm_.data_format = strdup( if (new_prm.raw_target) free(new_prm.raw_target);
config_["data_format"].isString() ? config_["data_format"].asString().c_str() : "ascii"); new_prm.raw_target = strdup(cfg["raw_target"].asString().c_str());
if (config_["raw_target"].isString()) {
if (prm_.raw_target) free(prm_.raw_target);
prm_.raw_target = strdup(config_["raw_target"].asString().c_str());
} }
prm_.ascii_range = config_["format-icons"].size() - 1; {
auto icon_count = cfg["format-icons"].size();
if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt(); new_prm.ascii_range = (icon_count > 0) ? static_cast<int>(icon_count) - 1 : 0;
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");
} }
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 if (cfg["framerate"].isInt()) new_prm.framerate = cfg["framerate"].asInt();
// Init cava plan, audio_raw structure if (cfg["autosens"].isInt()) new_prm.autosens = cfg["autosens"].asInt();
audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_); if (cfg["sensitivity"].isInt()) new_prm.sens = cfg["sensitivity"].asInt();
if (!plan_) spdlog::error("cava backend plan is not provided"); if (cfg["bars"].isInt()) new_prm.fixedbars = cfg["bars"].asInt();
audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message 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; prm_.output = output;
m_signal_config_changed_.emit();
} }
const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; } const ::cava::config_params& waybar::modules::cava::CavaBackend::getPrm() const {
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() { std::lock_guard<std::recursive_mutex> lock(state_mutex_);
return frame_time_milsec_; return prm_;
}; }
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() const {
std::lock_guard<std::recursive_mutex> lock(state_mutex_);
return adaptive_delay_.current();
}