From 7ccb76935d2dd644b2171e08678d7706b828b4d8 Mon Sep 17 00:00:00 2001 From: Alexis Date: Thu, 9 Aug 2018 23:55:38 +0200 Subject: [PATCH] feat: add pulseaudio module --- include/factory.hpp | 1 + include/modules/network.hpp | 14 ----- include/modules/pulseaudio.hpp | 36 +++++++++++ meson.build | 2 + resources/config | 6 +- resources/style.css | 7 ++- src/factory.cpp | 2 + src/modules/memory.cpp | 7 ++- src/modules/pulseaudio.cpp | 111 +++++++++++++++++++++++++++++++++ 9 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 include/modules/pulseaudio.hpp create mode 100644 src/modules/pulseaudio.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 261cc751..4ef56aae 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -7,6 +7,7 @@ #include "modules/memory.hpp" #include "modules/cpu.hpp" #include "modules/network.hpp" +#include "modules/pulseaudio.hpp" namespace waybar { diff --git a/include/modules/network.hpp b/include/modules/network.hpp index eda41f37..35ee4e69 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -20,20 +20,6 @@ namespace waybar::modules { Network(Json::Value config); auto update() -> void; operator Gtk::Widget &(); - typedef struct { - int flags; - char essid[IW_ESSID_MAX_SIZE + 1]; - uint8_t bssid[ETH_ALEN]; - int quality; - int quality_max; - int quality_average; - int signal_level; - int signal_level_max; - int noise_level; - int noise_level_max; - int bitrate; - double frequency; - } wireless_info_t; private: void _parseEssid(struct nlattr **bss); void _parseSignal(struct nlattr **bss); diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp new file mode 100644 index 00000000..35df8cc9 --- /dev/null +++ b/include/modules/pulseaudio.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "util/chrono.hpp" +#include "IModule.hpp" + +namespace waybar::modules { + + class Pulseaudio : public IModule { + public: + Pulseaudio(Json::Value config); + auto update() -> void; + operator Gtk::Widget &(); + private: + static void _subscribeCb(pa_context *context, + pa_subscription_event_type_t type, uint32_t idx, void *data); + static void _contextStateCb(pa_context *c, void *data); + static void _sinkInfoCb(pa_context *context, const pa_sink_info *i, + int eol, void *data); + static void _serverInfoCb(pa_context *context, const pa_server_info *i, + void *data); + Gtk::Label _label; + std::thread _thread; + Json::Value _config; + pa_mainloop *_mainloop; + pa_mainloop_api *_mainloop_api; + pa_context *_context; + int _volume; + bool _muted; + }; + +} diff --git a/meson.build b/meson.build index 2994327d..127a6dc6 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libnl = dependency('libnl-3.0') libnlgen = dependency('libnl-genl-3.0') +libpulse = dependency('libpulse') subdir('protocol') @@ -47,6 +48,7 @@ executable( gtkmm, libnl, libnlgen, + libpulse, ], include_directories: [include_directories('include')], install: true, diff --git a/resources/config b/resources/config index e499920b..fe53e63d 100644 --- a/resources/config +++ b/resources/config @@ -1,6 +1,6 @@ { "modules-left": ["workspaces"], - "modules-right": ["network", "cpu", "memory", "battery", "clock"], + "modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "clock"], "cpu": { "format": "{}% " }, @@ -13,5 +13,9 @@ "network": { "interface": "wlp2s0", "format": "{essid} ({signalStrength}dBm) " + }, + "pulseaudio": { + "format": "{}% ", + "format-muted": "" } } diff --git a/resources/style.css b/resources/style.css index b7a22e11..e0581224 100644 --- a/resources/style.css +++ b/resources/style.css @@ -23,7 +23,7 @@ window { border-bottom: 3px solid white; } -.clock, .battery, .cpu, .memory, .network { +.clock, .battery, .cpu, .memory, .network, .pulseaudio { padding: 0 10px; margin: 0 5px; } @@ -54,3 +54,8 @@ window { .network { background: #2980b9; } + +.pulseaudio { + background: #f1c40f; + color: black; +} diff --git a/src/factory.cpp b/src/factory.cpp index 82cc951b..54589c5a 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -18,5 +18,7 @@ waybar::IModule &waybar::Factory::makeModule(std::string name) return *new waybar::modules::Clock(_config[name]); if (name == "network") return *new waybar::modules::Network(_config[name]); + if (name == "pulseaudio") + return *new waybar::modules::Pulseaudio(_config[name]); throw std::runtime_error("Unknown module: " + name); } diff --git a/src/modules/memory.cpp b/src/modules/memory.cpp index 17df1507..8d5274f1 100644 --- a/src/modules/memory.cpp +++ b/src/modules/memory.cpp @@ -1,5 +1,4 @@ #include "modules/memory.hpp" -#include waybar::modules::Memory::Memory(Json::Value config) : _config(config) @@ -16,10 +15,12 @@ auto waybar::modules::Memory::update() -> void { struct sysinfo info; if (!sysinfo(&info)) { - int used_ram_percentage = 100 * (info.totalram - info.freeram) / info.totalram; + auto total = info.totalram * info.mem_unit; + auto freeram = info.freeram * info.mem_unit; + int used_ram_percentage = 100 * (total - freeram) / total; auto format = _config["format"] ? _config["format"].asString() : "{}%"; _label.set_text(fmt::format(format, used_ram_percentage)); - auto used_ram_gigabytes = (info.totalram - info.freeram) / std::pow(1024, 3); + auto used_ram_gigabytes = (total - freeram) / std::pow(1024, 3); _label.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); } } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp new file mode 100644 index 00000000..28da1b38 --- /dev/null +++ b/src/modules/pulseaudio.cpp @@ -0,0 +1,111 @@ +#include "modules/pulseaudio.hpp" + +#include + +waybar::modules::Pulseaudio::Pulseaudio(Json::Value config) + : _config(config), _mainloop(nullptr), _mainloop_api(nullptr), + _context(nullptr), _volume(0), _muted(false) +{ + _label.get_style_context()->add_class("pulseaudio"); + _mainloop = pa_mainloop_new(); + if (!_mainloop) + throw std::runtime_error("pa_mainloop_new() failed."); + _mainloop_api = pa_mainloop_get_api(_mainloop); + if (pa_signal_init(_mainloop_api) != 0) + throw std::runtime_error("pa_signal_init() failed."); + _context = pa_context_new(_mainloop_api, "waybar"); + if (!_context) + throw std::runtime_error("pa_context_new() failed."); + if (pa_context_connect(_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) + throw std::runtime_error(fmt::format("pa_context_connect() failed: {}", + pa_strerror(pa_context_errno(_context)))); + pa_context_set_state_callback(_context, _contextStateCb, this); + _thread = std::thread([this]() { + if (pa_mainloop_run(_mainloop, nullptr) < 0) + throw std::runtime_error("pa_mainloop_run() failed."); + }); +}; + +void waybar::modules::Pulseaudio::_contextStateCb(pa_context *c, void *data) +{ + auto pa = static_cast(data); + switch (pa_context_get_state(c)) { + case PA_CONTEXT_TERMINATED: + pa->_mainloop_api->quit(pa->_mainloop_api, 0); + break; + case PA_CONTEXT_READY: + pa_context_get_server_info(c, _serverInfoCb, data); + pa_context_set_subscribe_callback(c, _subscribeCb, data); + pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, nullptr, + nullptr); + break; + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + case PA_CONTEXT_FAILED: + default: + pa->_mainloop_api->quit(pa->_mainloop_api, 1); + break; + } +} + +/* + * Called when an event we subscribed to occurs. + */ +void waybar::modules::Pulseaudio::_subscribeCb(pa_context *context, + pa_subscription_event_type_t type, uint32_t idx, void *data) +{ + unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + pa_operation *op = nullptr; + + switch (facility) { + case PA_SUBSCRIPTION_EVENT_SINK: + pa_context_get_sink_info_by_index(context, idx, _sinkInfoCb, data); + break; + default: + assert(0); + break; + } + if (op) + pa_operation_unref(op); +} + +/* + * Called when the requested sink information is ready. + */ +void waybar::modules::Pulseaudio::_sinkInfoCb(pa_context *context, + const pa_sink_info *i, int eol, void *data) +{ + if (i) { + auto pa = static_cast(data); + float volume = (float)pa_cvolume_avg(&(i->volume)) / (float)PA_VOLUME_NORM; + pa->_volume = volume * 100.0f; + pa->_muted = i->mute; + pa->update(); + } +} + +/* + * Called when the requested information on the server is ready. This is + * used to find the default PulseAudio sink. + */ +void waybar::modules::Pulseaudio::_serverInfoCb(pa_context *context, + const pa_server_info *i, void *data) +{ + pa_context_get_sink_info_by_name(context, i->default_sink_name, _sinkInfoCb, + data); +} + +auto waybar::modules::Pulseaudio::update() -> void +{ + auto format = _config["format"] ? _config["format"].asString() : "{}%"; + if (_muted) + format = + _config["format-muted"] ? _config["format-muted"].asString() : format; + _label.set_text(fmt::format(format, _volume)); +} + +waybar::modules::Pulseaudio::operator Gtk::Widget &() { + return _label; +}