Merge branch 'master' into battery-plugging-instant-updates

This commit is contained in:
Alexis Rouillard 2025-12-18 19:04:31 +01:00 committed by GitHub
commit c3d29b1185
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 605 additions and 172 deletions

View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1761588595,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1760878510, "lastModified": 1764242076,
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -56,6 +56,8 @@ class Client {
std::list<struct waybar_output> outputs_; std::list<struct waybar_output> outputs_;
std::unique_ptr<CssReloadHelper> m_cssReloadHelper; std::unique_ptr<CssReloadHelper> m_cssReloadHelper;
std::string m_cssFile; std::string m_cssFile;
sigc::connection monitor_added_connection_;
sigc::connection monitor_removed_connection_;
}; };
} // namespace waybar } // namespace waybar

View File

@ -49,6 +49,7 @@ class Battery : public ALabel {
std::string old_status_; std::string old_status_;
std::string last_event_; std::string last_event_;
bool warnFirstTime_{true}; bool warnFirstTime_{true};
bool weightedAverage_{true};
const Bar& bar_; const Bar& bar_;
util::SleeperThread thread_; util::SleeperThread thread_;

View File

@ -0,0 +1,43 @@
#pragma once
#include <epoxy/gl.h>
#include "AModule.hpp"
#include "cava_backend.hpp"
namespace waybar::modules::cava {
class CavaGLSL final : public AModule, public Gtk::GLArea {
public:
CavaGLSL(const std::string&, const Json::Value&);
~CavaGLSL() = default;
private:
std::shared_ptr<CavaBackend> backend_;
struct ::cava::config_params prm_;
int frame_counter{0};
bool silence_{false};
bool hide_on_silence_{false};
// Cava method
auto onUpdate(const ::cava::audio_raw& input) -> void;
auto onSilence() -> void;
// Member variable to store the shared pointer
std::shared_ptr<::cava::audio_raw> m_data_;
GLuint shaderProgram_;
// OpenGL variables
GLuint fbo_;
GLuint texture_;
GLint uniform_bars_;
GLint uniform_previous_bars_;
GLint uniform_bars_count_;
GLint uniform_time_;
// Methods
void onRealize();
bool onRender(const Glib::RefPtr<Gdk::GLContext>& context);
void initShaders();
void initSurface();
void initGLSL();
GLuint loadShader(const std::string& fileName, GLenum type);
};
} // namespace waybar::modules::cava

View File

@ -9,20 +9,20 @@ class Cava final : public ALabel, public sigc::trackable {
public: public:
Cava(const std::string&, const Json::Value&); Cava(const std::string&, const Json::Value&);
~Cava() = default; ~Cava() = default;
auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void;
auto doAction(const std::string& name) -> void override; auto doAction(const std::string& name) -> void override;
private: private:
std::shared_ptr<CavaBackend> backend_; std::shared_ptr<CavaBackend> backend_;
// Text to display // Text to display
std::string label_text_{""}; Glib::ustring label_text_{""};
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}; int ascii_range_{0};
bool silence_{false};
// Cava method // Cava method
void pause_resume(); void pause_resume();
auto onUpdate(const std::string& input) -> void;
auto onSilence() -> void;
// ModuleActionMap // ModuleActionMap
static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()> static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()>
actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}};

View File

@ -32,16 +32,22 @@ class CavaBackend final {
int getAsciiRange(); int getAsciiRange();
void doPauseResume(); void doPauseResume();
void Update(); void Update();
const struct ::cava::config_params* getPrm();
std::chrono::milliseconds getFrameTimeMilsec();
// Signal accessor // Signal accessor
using type_signal_update = sigc::signal<void(const std::string&)>; using type_signal_update = sigc::signal<void(const std::string&)>;
type_signal_update signal_update(); 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()>; using type_signal_silence = sigc::signal<void()>;
type_signal_silence signal_silence(); type_signal_silence signal_silence();
private: private:
CavaBackend(const Json::Value& config); CavaBackend(const Json::Value& config);
util::SleeperThread thread_;
util::SleeperThread read_thread_; util::SleeperThread read_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_{NULL};
@ -55,6 +61,7 @@ class CavaBackend final {
// Delay to handle audio source // Delay to handle audio source
std::chrono::milliseconds frame_time_milsec_{1s}; std::chrono::milliseconds frame_time_milsec_{1s};
const 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};
@ -66,9 +73,12 @@ class CavaBackend final {
void execute(); void execute();
bool isSilence(); bool isSilence();
void doUpdate(bool force = false); void doUpdate(bool force = false);
void loadConfig();
void freeBackend();
// Signal // Signal
type_signal_update m_signal_update_; type_signal_update m_signal_update_;
type_signal_audio_raw_update m_signal_audio_raw_;
type_signal_silence m_signal_silence_; type_signal_silence m_signal_silence_;
}; };
} // namespace waybar::modules::cava } // namespace waybar::modules::cava

View File

@ -0,0 +1,30 @@
#pragma once
#ifdef HAVE_LIBCAVA
#include "cavaRaw.hpp"
#include "cava_backend.hpp"
#ifdef HAVE_LIBCAVAGLSL
#include "cavaGLSL.hpp"
#endif
#endif
namespace waybar::modules::cava {
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) {
case ::cava::output_method::OUTPUT_RAW:
return new waybar::modules::cava::Cava(id, config);
break;
#ifdef HAVE_LIBCAVAGLSL
case ::cava::output_method::OUTPUT_SDL_GLSL:
return new waybar::modules::cava::CavaGLSL(id, config);
break;
#endif
default:
break;
}
#endif
throw std::runtime_error("Unknown module");
};
} // namespace waybar::modules::cava

View File

@ -91,6 +91,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y
typeof: string ++ typeof: string ++
Command to execute when scrolling up on the module. Command to execute when scrolling up on the module.
*weighted-average*: ++
typeof: bool ++
default: true ++
Option to combine multiple batteries with different capacities.
*on-scroll-down*: ++ *on-scroll-down*: ++
typeof: string ++ typeof: string ++
Command to execute when scrolling down on the module. Command to execute when scrolling down on the module.
@ -175,7 +180,7 @@ The *battery* module allows one to define custom formats based on up to two fact
Every entry in the *events* object consists of a *<event-name>* (typeof: *string*) and a *<command>* (typeof: *string*). ++ Every entry in the *events* object consists of a *<event-name>* (typeof: *string*) and a *<command>* (typeof: *string*). ++
*<event-name>* can be in one of the following formats: *<event-name>* can be in one of the following formats:
- *on-<status>-<state>* - *on-<status>-<state>*
- *on-<status>-<capacity>* - *on-<status>-<capacity>*
Where: Where:

View File

@ -234,7 +234,6 @@ $text\\n$tooltip\\n$class*
``` ```
"custom/pacman": { "custom/pacman": {
"format": "{text} ", "format": "{text} ",
"interval": 3600, // every hour
"exec": "checkupdates | wc -l", // # of updates "exec": "checkupdates | wc -l", // # of updates
"exec-if": "exit 0", // always run; consider advanced run conditions "exec-if": "exit 0", // always run; consider advanced run conditions
"on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system "on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system
@ -242,7 +241,7 @@ $text\\n$tooltip\\n$class*
} }
``` ```
You can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*. Under the premise that interval is not defined, you can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*.
# STYLE # STYLE

View File

@ -173,8 +173,8 @@ to be selected when the corresponding audio device is muted. This applies to *de
"format-icons": { "format-icons": {
"alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "",
"headphones": "", "headphone": "",
"handsfree": "", "hands-free": "",
"headset": "", "headset": "",
"phone": "", "phone": "",
"phone-muted": "", "phone-muted": "",

View File

@ -497,16 +497,24 @@ else
man_files += files('man/waybar-clock.5.scd') man_files += files('man/waybar-clock.5.scd')
endif endif
cava = dependency('cava', cava = dependency('libcava',
version : '>=0.10.6', version : '>=0.10.7',
required: get_option('cava'), required: get_option('cava'),
fallback : ['cava', 'cava_dep'], fallback : ['libcava', 'cava_dep'],
not_found_message: 'cava is not found. Building waybar without cava') not_found_message: 'cava is not found. Building waybar without cava')
eproxy = dependency('epoxy', required: false)
if cava.found() if cava.found()
add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')
src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') src_files += files('src/modules/cava/cavaRaw.cpp',
'src/modules/cava/cava_backend.cpp')
man_files += files('man/waybar-cava.5.scd') man_files += files('man/waybar-cava.5.scd')
if eproxy.found()
add_project_arguments('-DHAVE_LIBCAVAGLSL', language: 'cpp')
src_files += files('src/modules/cava/cavaGLSL.cpp')
endif
endif endif
if libgps.found() if libgps.found()
@ -554,6 +562,7 @@ executable(
tz_dep, tz_dep,
xkbregistry, xkbregistry,
cava, cava,
eproxy,
libgps libgps
], ],
include_directories: inc_dirs, include_directories: inc_dirs,

View File

@ -219,13 +219,22 @@ void waybar::Client::bindInterfaces() {
if (xdg_output_manager == nullptr) { if (xdg_output_manager == nullptr) {
throw std::runtime_error("Failed to acquire required resources."); throw std::runtime_error("Failed to acquire required resources.");
} }
// Disconnect previous signal handlers to prevent duplicate handlers on reload
monitor_added_connection_.disconnect();
monitor_removed_connection_.disconnect();
// Clear stale outputs from previous run
outputs_.clear();
// add existing outputs and subscribe to updates // add existing outputs and subscribe to updates
for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) {
auto monitor = gdk_display->get_monitor(i); auto monitor = gdk_display->get_monitor(i);
handleMonitorAdded(monitor); handleMonitorAdded(monitor);
} }
gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); monitor_added_connection_ = gdk_display->signal_monitor_added().connect(
gdk_display->signal_monitor_removed().connect( sigc::mem_fun(*this, &Client::handleMonitorAdded));
monitor_removed_connection_ = gdk_display->signal_monitor_removed().connect(
sigc::mem_fun(*this, &Client::handleMonitorRemoved)); sigc::mem_fun(*this, &Client::handleMonitorRemoved));
} }

View File

@ -108,15 +108,13 @@
#ifdef HAVE_LIBWIREPLUMBER #ifdef HAVE_LIBWIREPLUMBER
#include "modules/wireplumber.hpp" #include "modules/wireplumber.hpp"
#endif #endif
#ifdef HAVE_LIBCAVA
#include "modules/cava/cava.hpp"
#endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
#include "modules/systemd_failed_units.hpp" #include "modules/systemd_failed_units.hpp"
#endif #endif
#ifdef HAVE_LIBGPS #ifdef HAVE_LIBGPS
#include "modules/gps.hpp" #include "modules/gps.hpp"
#endif #endif
#include "modules/cava/cava_frontend.hpp"
#include "modules/cffi.hpp" #include "modules/cffi.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/image.hpp" #include "modules/image.hpp"
@ -341,11 +339,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
return new waybar::modules::Wireplumber(id, config_[name]); return new waybar::modules::Wireplumber(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBCAVA
if (ref == "cava") { if (ref == "cava") {
return new waybar::modules::cava::Cava(id, config_[name]); return waybar::modules::cava::getModule(id, config_[name]);
} }
#endif
#ifdef HAVE_SYSTEMD_MONITOR #ifdef HAVE_SYSTEMD_MONITOR
if (ref == "systemd-failed-units") { if (ref == "systemd-failed-units") {
return new waybar::modules::SystemdFailedUnits(id, config_[name]); return new waybar::modules::SystemdFailedUnits(id, config_[name]);

View File

@ -26,7 +26,7 @@ Gtk::RevealerTransitionType getPreferredTransitionType(bool is_vertical) {
Group::Group(const std::string& name, const std::string& id, const Json::Value& config, Group::Group(const std::string& name, const std::string& id, const Json::Value& config,
bool vertical) bool vertical)
: AModule(config, name, id, true, true), : AModule(config, name, id, true, false),
box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {
box.set_name(name_); box.set_name(name_);

View File

@ -32,6 +32,8 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
throw std::runtime_error("udev failed to add monitor filter"); throw std::runtime_error("udev failed to add monitor filter");
} }
udev_monitor_enable_receiving(mon_.get()); udev_monitor_enable_receiving(mon_.get());
if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool();
#endif #endif
spdlog::debug("battery: worker interval is {}", interval_.count()); spdlog::debug("battery: worker interval is {}", interval_.count());
worker(); worker();
@ -591,8 +593,7 @@ waybar::modules::Battery::getInfos() {
} }
// Handle weighted-average // Handle weighted-average
if ((config_["weighted-average"].isBool() ? config_["weighted-average"].asBool() : false) && if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {
total_energy_exists && total_energy_full_exists) {
if (total_energy_full > 0.0f) if (total_energy_full > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
} }

View File

@ -0,0 +1,271 @@
#include "modules/cava/cavaGLSL.hpp"
#include <spdlog/spdlog.h>
#include <fstream>
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_);
if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool();
if (!id.empty()) {
get_style_context()->add_class(id);
}
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);
// Get parameters_config struct from the backend
prm_ = *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;
set_size_request(length, prm_.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);
}
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;
}
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 (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();
}
});
}
bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr<Gdk::GLContext>& context) {
if (!m_data_) return true;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_);
glUniform1i(glGetUniformLocation(shaderProgram_, "inputTexture"), 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);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
void waybar::modules::cava::CavaGLSL::onRealize() {
make_current();
initShaders();
initGLSL();
initSurface();
}
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);
}
}
void waybar::modules::cava::CavaGLSL::initGLSL() {
GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, "vertexPosition_modelspace")};
if (gVertexPos2DLocation == -1) {
spdlog::error("{0}. Could not find vertex position shader variable", name_);
}
glClearColor(0.f, 0.f, 0.f, 1.f);
GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};
GLint indexData[]{0, 1, 2, 3};
GLuint gVBO{0};
glGenBuffers(1, &gVBO);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);
GLuint gIBO{0};
glGenBuffers(1, &gIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);
GLuint gVAO{0};
glGenVertexArrays(1, &gVAO);
glBindVertexArray(gVAO);
glEnableVertexAttribArray(gVertexPos2DLocation);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);
glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);
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,
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");
GLuint err{glGetError()};
if (err != 0) {
spdlog::error("{0}. Error on initGLSL: {1}", name_, err);
}
}
void waybar::modules::cava::CavaGLSL::initSurface() {
colors color = {0};
GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")};
parse_color(prm_.bcolor, &color);
glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0,
(float)color.B / 255.0);
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);
GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")};
glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f);
GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")};
glUniform1i(uniform_bar_width, prm_.bar_width);
GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")};
glUniform1i(uniform_bar_spacing, prm_.bar_spacing);
GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "gradient_count")};
glUniform1i(uniform_gradient_count, prm_.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;
}
glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);
}
void waybar::modules::cava::CavaGLSL::initShaders() {
shaderProgram_ = glCreateProgram();
GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)};
GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)};
glAttachShader(shaderProgram_, vertexShader);
glAttachShader(shaderProgram_, fragmentShader);
glLinkProgram(shaderProgram_);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Check for linking errors
GLint success, len;
glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);
if (!success) {
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);
}
glReleaseShaderCompiler();
glUseProgram(shaderProgram_);
}
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);
}
std::ostringstream buffer;
buffer << shaderFile.rdbuf(); // read file content into stringstream
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);
glShaderSource(shaderID, 1, &shaderSource, nullptr);
glCompileShader(shaderID);
// Check for compilation errors
GLint success, len;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
if (!success) {
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);
}
return shaderID;
}

View File

@ -1,4 +1,4 @@
#include "modules/cava/cava.hpp" #include "modules/cava/cavaRaw.hpp"
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -24,28 +24,35 @@ auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void {
// Cava actions // Cava actions
void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }
auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {
if (silence_) { Glib::signal_idle().connect_once([this, input]() {
label_.get_style_context()->remove_class("silent"); if (silence_) {
label_.get_style_context()->add_class("updated"); label_.get_style_context()->remove_class("silent");
} if (!label_.get_style_context()->has_class("updated"))
label_text_.clear(); label_.get_style_context()->add_class("updated");
for (auto& ch : input) }
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1)); label_text_.clear();
for (auto& ch : input)
label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1));
label_.set_markup(label_text_); label_.set_markup(label_text_);
label_.show(); label_.show();
ALabel::update(); ALabel::update();
});
silence_ = false; silence_ = false;
} }
auto waybar::modules::cava::Cava::onSilence() -> void {
if (!silence_) {
label_.get_style_context()->remove_class("updated");
if (hide_on_silence_) auto waybar::modules::cava::Cava::onSilence() -> void {
label_.hide(); Glib::signal_idle().connect_once([this]() {
else if (config_["format_silent"].isString()) if (!silence_) {
label_.set_markup(format_silent_); if (label_.get_style_context()->has_class("updated"))
silence_ = true; label_.get_style_context()->remove_class("updated");
label_.get_style_context()->add_class("silent");
} if (hide_on_silence_)
label_.hide();
else if (config_["format_silent"].isString())
label_.set_markup(format_silent_);
silence_ = true;
label_.get_style_context()->add_class("silent");
}
});
} }

View File

@ -9,91 +9,9 @@ std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaB
return backend_ptr; return backend_ptr;
} }
waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) {
// Load waybar module config // Load waybar module config
char cfgPath[PATH_MAX]; loadConfig();
cfgPath[0] = '\0';
if (config["cava_config"].isString()) strcpy(cfgPath, config["cava_config"].asString().data());
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, false, &error_, 0)) {
spdlog::error("cava backend. Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
prm_.output = ::cava::output_method::OUTPUT_RAW;
if (prm_.data_format) free(prm_.data_format);
prm_.data_format = strdup("ascii");
if (prm_.raw_target) free(prm_.raw_target);
prm_.raw_target = strdup("/dev/stdout");
prm_.ascii_range = config["format-icons"].size() - 1;
prm_.bar_width = 2;
prm_.bar_spacing = 0;
prm_.bar_height = 32;
prm_.bar_width = 1;
prm_.orientation = ::cava::ORIENT_TOP;
prm_.xaxis = ::cava::xaxis_scale::NONE;
prm_.mono_opt = ::cava::AVERAGE;
prm_.autobars = 0;
prm_.gravity = 0;
prm_.integral = 1;
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 = config["source"].asString().data();
}
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());
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_) {
spdlog::error("cava backend API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
// 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
// Read audio source trough cava API. Cava orginizes this process via infinity loop // Read audio source trough cava API. Cava orginizes this process via infinity loop
read_thread_ = [this] { read_thread_ = [this] {
try { try {
@ -102,41 +20,38 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) {
spdlog::warn("Cava backend. Read source error: {0}", e.what()); spdlog::warn("Cava backend. Read source error: {0}", e.what());
} }
read_thread_.sleep_for(fetch_input_delay_); read_thread_.sleep_for(fetch_input_delay_);
loadConfig();
}; };
// Write outcoming data. Emit signals
thread_ = [this] { out_thread_ = [this] {
doUpdate(); doUpdate(false);
thread_.sleep_for(frame_time_milsec_); out_thread_.sleep_for(frame_time_milsec_);
}; };
} }
waybar::modules::cava::CavaBackend::~CavaBackend() { waybar::modules::cava::CavaBackend::~CavaBackend() {
thread_.stop(); out_thread_.stop();
read_thread_.stop(); read_thread_.stop();
cava_destroy(plan_);
delete plan_; freeBackend();
plan_ = nullptr;
audio_raw_clean(&audio_raw_);
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
config_clean(&prm_);
free(audio_data_.source);
free(audio_data_.cava_in);
} }
static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta == std::chrono::seconds{0}) { if (delta == std::chrono::seconds{0}) {
delta += std::chrono::seconds{1}; delta += std::chrono::seconds{1};
delay += delta; delay += delta;
return true;
} }
return false;
} }
static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { static bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
if (delta > std::chrono::seconds{0}) { if (delta > std::chrono::seconds{0}) {
delay -= delta; delay -= delta;
delta -= std::chrono::seconds{1}; delta -= std::chrono::seconds{1};
return true;
} }
return false;
} }
bool waybar::modules::cava::CavaBackend::isSilence() { bool waybar::modules::cava::CavaBackend::isSilence() {
@ -186,6 +101,7 @@ void waybar::modules::cava::CavaBackend::doPauseResume() {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_); upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
} }
pthread_mutex_unlock(&audio_data_.lock); pthread_mutex_unlock(&audio_data_.lock);
Update();
} }
waybar::modules::cava::CavaBackend::type_signal_update waybar::modules::cava::CavaBackend::type_signal_update
@ -193,6 +109,11 @@ waybar::modules::cava::CavaBackend::signal_update() {
return m_signal_update_; return m_signal_update_;
} }
waybar::modules::cava::CavaBackend::type_signal_audio_raw_update
waybar::modules::cava::CavaBackend::signal_audio_raw_update() {
return m_signal_audio_raw_;
}
waybar::modules::cava::CavaBackend::type_signal_silence waybar::modules::cava::CavaBackend::type_signal_silence
waybar::modules::cava::CavaBackend::signal_silence() { waybar::modules::cava::CavaBackend::signal_silence() {
return m_signal_silence_; return m_signal_silence_;
@ -215,12 +136,138 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) {
} }
if (!silence_ || prm_.sleep_timer == 0) { if (!silence_ || prm_.sleep_timer == 0) {
downThreadDelay(frame_time_milsec_, suspend_silence_delay_); if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
execute(); execute();
if (re_paint_ == 1 || force) m_signal_update_.emit(output_); if (re_paint_ == 1 || force || prm_.continuous_rendering) {
m_signal_update_.emit(output_);
m_signal_audio_raw_.emit(audio_raw_);
}
} else { } else {
upThreadDelay(frame_time_milsec_, suspend_silence_delay_); if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();
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() {
if (plan_ != NULL) {
cava_destroy(plan_);
plan_ = NULL;
}
audio_raw_clean(&audio_raw_);
pthread_mutex_lock(&audio_data_.lock);
audio_data_.terminate = 1;
pthread_mutex_unlock(&audio_data_.lock);
free_config(&prm_);
free(audio_data_.source);
free(audio_data_.cava_in);
}
void waybar::modules::cava::CavaBackend::loadConfig() {
freeBackend();
// Load waybar module config
char cfgPath[PATH_MAX];
cfgPath[0] = '\0';
if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data());
// Load cava config
error_.length = 0;
if (!load_config(cfgPath, &prm_, &error_)) {
spdlog::error("cava backend. Error loading config. {0}", error_.message);
exit(EXIT_FAILURE);
}
// Override cava parameters by the user config
prm_.inAtty = 0;
auto const output{prm_.output};
// prm_.output = ::cava::output_method::OUTPUT_RAW;
if (config_["data_format"].isString()) {
if (prm_.data_format) free(prm_.data_format);
prm_.data_format = strdup(config_["data_format"].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;
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 = config_["source"].asString().data();
}
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_) {
spdlog::error("cava backend API didn't provide input audio source method");
exit(EXIT_FAILURE);
}
prm_.output = ::cava::output_method::OUTPUT_RAW;
// 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
prm_.output = output;
}
const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; }
std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() {
return frame_time_milsec_;
};

View File

@ -36,7 +36,7 @@ auto waybar::modules::Memory::update() -> void {
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76); float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
int used_swap_percentage = 0; int used_swap_percentage = 0;
if (swaptotal && swapfree) { if (swaptotal) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
} }
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76); float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);

View File

@ -14,16 +14,18 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
mixer_api_(nullptr), mixer_api_(nullptr),
def_nodes_api_(nullptr), def_nodes_api_(nullptr),
default_node_name_(nullptr), default_node_name_(nullptr),
default_source_name_(nullptr),
pending_plugins_(0), pending_plugins_(0),
muted_(false), muted_(false),
source_muted_(false),
volume_(0.0), volume_(0.0),
source_volume_(0.0),
min_step_(0.0), min_step_(0.0),
node_id_(0), node_id_(0),
node_name_(""),
source_name_(""),
type_(nullptr),
source_node_id_(0), source_node_id_(0),
type_(nullptr) { source_muted_(false),
source_volume_(0.0),
default_source_name_(nullptr) {
waybar::modules::Wireplumber::modules.push_back(this); waybar::modules::Wireplumber::modules.push_back(this);
wp_init(WP_INIT_PIPEWIRE); wp_init(WP_INIT_PIPEWIRE);

View File

@ -1,11 +0,0 @@
[wrap-git]
url = https://github.com/LukashonakV/cava.git
revision = 23efcced43b5a395747b18a2e5f2171fc0925d18
depth = 1
#directory = cava-0.10.6
#source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz
#source_filename = cava-0.10.6.tar.gz
#source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4
[provide]
cava = cava_dep

12
subprojects/libcava.wrap Normal file
View File

@ -0,0 +1,12 @@
#[wrap-git]
#url = https://github.com/LukashonakV/cava.git
#revision = 866cfec40b7b9d38e97148d004d3134c1385b52f
#depth = 1
[wrap-file]
directory = cava-0.10.7-beta
source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz
source_filename = cava-0.10.7.tar.gz
source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4
[provide]
libcava = cava_dep