diff --git a/include/factory.hpp b/include/factory.hpp index 3855ce22..8702632d 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -62,6 +62,9 @@ # include "modules/bluetooth.hpp" # endif #endif +#ifdef HAVE_LIBJACK +#include "modules/jack.hpp" +#endif namespace waybar { diff --git a/include/modules/jack.hpp b/include/modules/jack.hpp new file mode 100644 index 00000000..df020964 --- /dev/null +++ b/include/modules/jack.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include "ALabel.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class JACK : public ALabel { + public: + JACK(const std::string&, const Json::Value&); + ~JACK() = default; + auto update() -> void; + jack_nframes_t bufsize_; + jack_client_t* client_; + unsigned int xruns_; + std::string state_; + + private: + std::string JACKState(); + + jack_nframes_t samplerate_; + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 2c644cc3..33085903 100644 --- a/meson.build +++ b/meson.build @@ -97,6 +97,8 @@ libudev = dependency('libudev', required: get_option('libudev')) libevdev = dependency('libevdev', required: get_option('libevdev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) xkbregistry = dependency('xkbregistry') +libjack = dependency('jack', required: get_option('jack')) +libprocps = dependency('libprocps', required: get_option('jack')) libsndio = compiler.find_library('sndio', required: get_option('sndio')) if libsndio.found() @@ -207,6 +209,11 @@ if libpulse.found() src_files += 'src/modules/pulseaudio.cpp' endif +if libjack.found() and libprocps.found() + add_project_arguments('-DHAVE_LIBJACK', language: 'cpp') + src_files += 'src/modules/jack.cpp' +endif + if dbusmenu_gtk.found() add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp') src_files += files( @@ -288,6 +295,8 @@ executable( libnl, libnlgen, libpulse, + libjack, + libprocps, libudev, libepoll, libmpdclient, diff --git a/meson_options.txt b/meson_options.txt index 230a53d6..a41f70a6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,3 +13,5 @@ option('sndio', type: 'feature', value: 'auto', description: 'Enable support for option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') +option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK DSP load monitoring') + diff --git a/src/factory.cpp b/src/factory.cpp index 900653b5..30325fef 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -85,6 +85,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { return new waybar::modules::Pulseaudio(id, config_[name]); } #endif +#ifdef HAVE_LIBJACK + if (ref == "jack") { + return new waybar::modules::JACK(id, config_[name]); + } +#endif #ifdef HAVE_LIBMPDCLIENT if (ref == "mpd") { return new waybar::modules::MPD(id, config_[name]); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp new file mode 100644 index 00000000..08d14140 --- /dev/null +++ b/src/modules/jack.cpp @@ -0,0 +1,127 @@ +#include "modules/jack.hpp" + +extern "C" { + + int bufSizeCallback(unsigned int size, void *obj) { + waybar::modules::JACK* x = (waybar::modules::JACK*)obj; + x->bufsize_ = size; + return size; + } + + int xrunCallback(void *obj) { + waybar::modules::JACK* x = (waybar::modules::JACK*)obj; + x->xruns_ += 1; + x->state_ = "xrun"; + return 0; + } + + void shutdownCallback(void *obj) { + waybar::modules::JACK* x = (waybar::modules::JACK*)obj; + x->client_ = NULL; + x->state_ = "disconnected"; + x->xruns_ = 0; + } + +} + +waybar::modules::JACK::JACK(const std::string& id, const Json::Value& config) + : ALabel(config, "jack", id, "{load}%", 1) { + xruns_ = 0; + state_ = "disconnected"; + client_ = NULL; + + state_ = JACKState(); + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; +} + +std::string waybar::modules::JACK::JACKState() { + if(state_.compare("xrun") == 0) + return "xrun"; + if(state_.compare("connected") == 0) + return "connected"; + + std::string procname; + bool foundJACK = false; + proc_t** proctab = readproctab(PROC_FILLSTAT); + for(int i=0; proctab[i]; i++) { + procname = proctab[i]->cmd; + if(!procname.compare("jackd") || !procname.compare("pipewire")) + foundJACK = true; + freeproc(proctab[i]); + } + free(proctab); + if(!foundJACK) + return "disconnected"; + + client_ = jack_client_open("waybar", JackNoStartServer, NULL); + + if (client_) { + bufsize_ = jack_get_buffer_size(client_); + samplerate_ = jack_get_sample_rate(client_); + jack_set_buffer_size_callback(client_, bufSizeCallback, this); + jack_set_xrun_callback(client_, xrunCallback, this); + jack_on_shutdown(client_, shutdownCallback, this); + + if (!jack_activate(client_)) + return "connected"; + } + return "disconnected"; +} + +auto waybar::modules::JACK::update() -> void { + std::string format; + float latency = 1000 * (float)bufsize_ / (float)samplerate_; + auto state = JACKState(); + float load; + + if(label_.get_style_context()->has_class("xrun")) { + label_.get_style_context()->remove_class("xrun"); + state = "connected"; + } + + if(state.compare("disconnected") != 0) + load = jack_cpu_load(client_); + else { + load = 0; + bufsize_ = 0; + samplerate_ = 0; + latency = 0; + } + + if(label_.get_style_context()->has_class(state_)) + label_.get_style_context()->remove_class(state_); + + if (config_["format-" + state].isString()) { + format = config_["format-" + state].asString(); + } else if (config_["format"].isString()) { + format = config_["format"].asString(); + } else format = "DSP {load}%"; + + if(!label_.get_style_context()->has_class(state)) + label_.get_style_context()->add_class(state); + state_ = state; + + label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load)), + fmt::arg("bufsize", bufsize_), + fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), + fmt::arg("xruns", xruns_))); + + if (tooltipEnabled()) { + std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; + if (config_["tooltip-format"].isString()) + tooltip_format = config_["tooltip-format"].asString(); + label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("load", std::round(load)), + fmt::arg("bufsize", bufsize_), + fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), + fmt::arg("xruns", xruns_))); + + } + + // Call parent update + ALabel::update(); +}