From 116e26436bba32017364ed0a6ac183bf100e44ae Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Thu, 7 Sep 2017 20:27:21 -0700 Subject: [PATCH 01/16] wip(pulseaudio): create pulseaudio backend --- cmake/02-opts.cmake | 2 + cmake/03-libs.cmake | 1 + cmake/05-summary.cmake | 1 + include/adapters/pulseaudio.hpp | 73 +++++++ include/modules/volume.hpp | 9 +- include/settings.hpp.cmake | 6 +- src/CMakeLists.txt | 3 + src/adapters/pulseaudio.cpp | 355 ++++++++++++++++++++++++++++++++ src/modules/volume.cpp | 54 ++++- 9 files changed, 497 insertions(+), 7 deletions(-) create mode 100644 include/adapters/pulseaudio.hpp create mode 100644 src/adapters/pulseaudio.cpp diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake index adefa582b..535ff7825 100644 --- a/cmake/02-opts.cmake +++ b/cmake/02-opts.cmake @@ -7,6 +7,7 @@ checklib(ENABLE_CURL "pkg-config" libcurl) checklib(ENABLE_I3 "binary" i3) checklib(ENABLE_MPD "pkg-config" libmpdclient) checklib(ENABLE_NETWORK "cmake" Libiw) +checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse) checklib(WITH_XRM "pkg-config" xcb-xrm) checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12") checklib(WITH_XCURSOR "pkg-config" "xcb-cursor") @@ -28,6 +29,7 @@ option(ENABLE_I3 "Enable i3 support" ON) option(ENABLE_MPD "Enable mpd support" ON) option(ENABLE_NETWORK "Enable network support" ON) option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON) +option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON) option(WITH_XRANDR "xcb-randr support" ON) option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON) diff --git a/cmake/03-libs.cmake b/cmake/03-libs.cmake index 44ddf0985..be5d4b54b 100644 --- a/cmake/03-libs.cmake +++ b/cmake/03-libs.cmake @@ -11,6 +11,7 @@ querylib(ENABLE_ALSA "pkg-config" alsa libs dirs) querylib(ENABLE_CURL "pkg-config" libcurl libs dirs) querylib(ENABLE_MPD "pkg-config" libmpdclient libs dirs) querylib(ENABLE_NETWORK "cmake" Libiw libs dirs) +querylib(ENABLE_PULSEAUDIO "pkg-config" libpulse libs dirs) querylib(WITH_XCOMPOSITE "pkg-config" xcb-composite libs dirs) querylib(WITH_XDAMAGE "pkg-config" xcb-damage libs dirs) diff --git a/cmake/05-summary.cmake b/cmake/05-summary.cmake index 0e3924c9c..f9e426af8 100644 --- a/cmake/05-summary.cmake +++ b/cmake/05-summary.cmake @@ -18,6 +18,7 @@ colored_option(" curl" ENABLE_CURL) colored_option(" i3" ENABLE_I3) colored_option(" mpd" ENABLE_MPD) colored_option(" network" ENABLE_NETWORK) +colored_option(" pulseaudio" ENABLE_PULSEAUDIO) message(STATUS " X extensions:") colored_option(" xcb-randr" WITH_XRANDR) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp new file mode 100644 index 000000000..fb7ff022d --- /dev/null +++ b/include/adapters/pulseaudio.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "common.hpp" +#include "settings.hpp" +#include "errors.hpp" + +#include "utils/math.hpp" +// fwd +struct pa_context; +struct pa_threaded_mainloop; +struct pa_cvolume; +typedef struct pa_context pa_context; +typedef struct pa_threaded_mainloop pa_threaded_mainloop; + +POLYBAR_NS + +DEFINE_ERROR(pulseaudio_error); + +class pulseaudio { + // events to add to our queue + enum class evtype { NEW = 0, CHANGE, REMOVE }; + using queue = std::queue; + + public: + explicit pulseaudio(string&& sink_name); + ~pulseaudio(); + + pulseaudio(const pulseaudio& o) = delete; + pulseaudio& operator=(const pulseaudio& o) = delete; + + const string& get_name(); + + bool wait(int timeout = -1); + int process_events(); + + int get_volume(); + void set_volume(float percentage); + void set_mute(bool mode); + void toggle_mute(); + bool is_muted(); + + private: + static void check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); + static void get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int is_last, void *userdata); + static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata); + static void simple_callback(pa_context *context, int success, void *userdata); + static void get_default_sink_callback(pa_context *context, const pa_server_info *info, void *userdata); + static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); + static void context_state_callback(pa_context *context, void *userdata); + + // used for temporary callback results + pa_cvolume cv; + bool muted; + bool exists; + + pa_context* m_context{nullptr}; + pa_threaded_mainloop* m_mainloop{nullptr}; + + queue m_events; + + // specified sink name + string spec_s_name; + // sink currently in use + string s_name; + // default sink name + string def_s_name; + uint32_t s_index{0}; +}; + +POLYBAR_NS_END diff --git a/include/modules/volume.hpp b/include/modules/volume.hpp index 8c86665f2..92949e430 100644 --- a/include/modules/volume.hpp +++ b/include/modules/volume.hpp @@ -1,6 +1,7 @@ #pragma once #include "settings.hpp" +#include "adapters/pulseaudio.hpp" #include "modules/meta/event_module.hpp" #include "modules/meta/input_handler.hpp" @@ -11,6 +12,7 @@ namespace alsa { class mixer; class control; } +//class pulseaudio; namespace modules { enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE }; @@ -18,6 +20,7 @@ namespace modules { using mixer_t = shared_ptr; using control_t = shared_ptr; + using pulseaudio_t = shared_ptr; class volume_module : public event_module, public input_handler { public: @@ -56,8 +59,10 @@ namespace modules { map m_mixer; map m_ctrl; - int m_headphoneid{0}; - bool m_mapped{false}; + pulseaudio_t m_pulseaudio; + + //int m_headphoneid{0}; + //bool m_mapped{false}; atomic m_muted{false}; atomic m_headphones{false}; atomic m_volume{0}; diff --git a/include/settings.hpp.cmake b/include/settings.hpp.cmake index 00d318977..22fce3d61 100644 --- a/include/settings.hpp.cmake +++ b/include/settings.hpp.cmake @@ -22,6 +22,7 @@ #cmakedefine01 ENABLE_NETWORK #cmakedefine01 ENABLE_I3 #cmakedefine01 ENABLE_CURL +#cmakedefine01 ENABLE_PULSEAUDIO #cmakedefine01 WITH_XRANDR #cmakedefine01 WITH_XRENDER @@ -99,12 +100,13 @@ const auto version_details = [](const std::vector& args) { // clang-format off const auto print_build_info = [](bool extended = false) { printf("%s %s\n\n", APP_NAME, APP_VERSION); - printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork\n", + printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork %cpulseaudio\n", (ENABLE_ALSA ? '+' : '-'), (ENABLE_CURL ? '+' : '-'), (ENABLE_I3 ? '+' : '-'), (ENABLE_MPD ? '+' : '-'), - (ENABLE_NETWORK ? '+' : '-')); + (ENABLE_NETWORK ? '+' : '-'), + (ENABLE_PULSEAUDIO ? '+' : '-')); if (extended) { printf("\n"); printf("X extensions: %crandr (%cmonitors) %crender %cdamage %csync %ccomposite %cxkb %cxrm %cxcursor\n", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d2ef77fd5..251c28669 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,9 @@ if(NOT ENABLE_I3) list(REMOVE_ITEM files modules/i3.cpp) list(REMOVE_ITEM files utils/i3.cpp) endif() +if(NOT ENABLE_PULSEAUDIO) + list(REMOVE_ITEM files adapters/pulseaudio.cpp) +endif() if(NOT WITH_XRANDR) list(REMOVE_ITEM files x11/extensions/randr.cpp) endif() diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp new file mode 100644 index 000000000..8af56cf85 --- /dev/null +++ b/src/adapters/pulseaudio.cpp @@ -0,0 +1,355 @@ +#include "adapters/pulseaudio.hpp" + +// TODO possibly move all the callback functions to lambda functions +// also maybe use pa_operation_unref(op) +// create base volume backend class (mixer/control, pulseaudio inherits from base class) +// use index instead of name internally? +POLYBAR_NS + +/* Multichannel volumes: + * use pa_cvolume_max(), and pa_cvolume_scale() + * + * see https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/WritingVolumeControlUIs/ + */ + +/** + * Construct pulseaudio object + */ +pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { + m_mainloop = pa_threaded_mainloop_new(); + if (!m_mainloop) { + throw pulseaudio_error("Could not create pulseaudio threaded mainloop."); + } + pa_threaded_mainloop_lock(m_mainloop); + + m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "polybar"); + if (!m_context) { + pa_threaded_mainloop_unlock(m_mainloop); + pa_threaded_mainloop_free(m_mainloop); + throw pulseaudio_error("Could not create pulseaudio context."); + } + + pa_context_set_state_callback(m_context, context_state_callback, this); + + if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) { + pa_context_disconnect(m_context); + pa_context_unref(m_context); + pa_threaded_mainloop_unlock(m_mainloop); + pa_threaded_mainloop_free(m_mainloop); + throw pulseaudio_error("Could not connect pulseaudio context."); + } + + if (pa_threaded_mainloop_start(m_mainloop) < 0) { + pa_context_disconnect(m_context); + pa_context_unref(m_context); + pa_threaded_mainloop_unlock(m_mainloop); + pa_threaded_mainloop_free(m_mainloop); + throw pulseaudio_error("Could not start pulseaudio mainloop."); + } + + pa_threaded_mainloop_wait(m_mainloop); + if (pa_context_get_state(m_context) != PA_CONTEXT_READY) { + //goto error; + throw pulseaudio_error("Could not connect to pulseaudio server."); + } + + pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + if (exists) + s_name = sink_name; + else { + op = pa_context_get_server_info(m_context, get_default_sink_callback, this); + if (!op) { + throw pulseaudio_error("Failed to get pulseaudio server info."); + } + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + s_name = def_s_name; + // get the sink index + op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); + } + + op = pa_context_subscribe(m_context, static_cast(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER), + simple_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_context_set_subscribe_callback(m_context, subscribe_callback, this); + + pa_threaded_mainloop_unlock(m_mainloop); + +} + +/** + * Deconstruct pulseaudio + */ +pulseaudio::~pulseaudio() { + pa_threaded_mainloop_unlock(m_mainloop); + pa_threaded_mainloop_stop(m_mainloop); + pa_context_disconnect(m_context); + pa_context_unref(m_context); + pa_threaded_mainloop_free(m_mainloop); + +} + +/** + * Get sink name + */ +const string& pulseaudio::get_name() { + return s_name; +} + +/** + * Wait for events (timeout in ms) + */ +bool pulseaudio::wait(int timeout) { + // TODO wait for specified timeout + (void) timeout; + return m_events.size() > 0; +} + +/** + * Process queued pulseaudio events + */ +int pulseaudio::process_events() { + int ret = m_events.size(); + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *o{nullptr}; + // clear the queue + while (!m_events.empty()) { + switch (m_events.front()) { + // try to get specified sink + case evtype::NEW: + o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + if (exists) + s_name = spec_s_name; + break; + // get volume + case evtype::CHANGE: + o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + break; + // get default sink + case evtype::REMOVE: + o = pa_context_get_server_info(m_context, get_default_sink_callback, this); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + break; + } + m_events.pop(); + } + pa_threaded_mainloop_unlock(m_mainloop); + return ret; +} + +/** + * Get volume in percentage + */ +int pulseaudio::get_volume() { + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(m_mainloop); + } + pa_threaded_mainloop_unlock(m_mainloop); + // alternatively, user pa_cvolume_avg_mask() to average selected channels + //return math_util::percentage(pa_cvolume_avg(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); + return math_util::percentage(pa_cvolume_max(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); +} + +/** + * Set volume to given percentage + */ +//void pulseaudio::set_volume(int delta_perc) { +void pulseaudio::set_volume(float percentage) { + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); + pa_cvolume_scale(&cv, vol); + op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_threaded_mainloop_unlock(m_mainloop); +} + +/** + * Set mute state + */ +void pulseaudio::set_mute(bool mode) { + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *op = pa_context_set_sink_mute_by_name(m_context, s_name.c_str(), mode, simple_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_threaded_mainloop_unlock(m_mainloop); +} + +/** + * Toggle mute state + */ +void pulseaudio::toggle_mute() { + set_mute(!is_muted()); +} + +/** + * Get current mute state + */ +bool pulseaudio::is_muted() { + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), check_mute_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_threaded_mainloop_unlock(m_mainloop); + return muted; +} + +/** + * Callback when getting current mute state + */ +void pulseaudio::check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { + if (eol < 0) { + throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))}); + } + if (eol) + return; + pulseaudio* This = static_cast(userdata); + if (info) + This->muted = info->mute; + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Callback when getting volume + */ +void pulseaudio::get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { + if (eol < 0) { + throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))}); + } + if (eol) + return; + //pa_assert(info); + pulseaudio* This = static_cast(userdata); + if (info) + This->cv = info->volume; + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Callback when subscribing to changes + */ +void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { + pulseaudio *This = static_cast(userdata); + switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + case PA_SUBSCRIPTION_EVENT_NEW: + // if using the default sink, check if the new sink matches our specified sink + if (This->s_name == This->def_s_name && This->spec_s_name != This->def_s_name) { + printf("NEW\n"); + This->m_events.emplace(evtype::NEW); + } + break; + case PA_SUBSCRIPTION_EVENT_CHANGE: + if (idx == PA_INVALID_INDEX) { + throw pulseaudio_error("Invalid index given: " + string{pa_strerror(pa_context_errno(context))}); + } else if (idx == This->s_index) { + printf("CHANGE\n"); + This->m_events.emplace(evtype::CHANGE); + } + break; + case PA_SUBSCRIPTION_EVENT_REMOVE: + if (idx == This->s_index) { + printf("REMOVE\n"); + This->m_events.emplace(evtype::REMOVE); + } + break; + } + break; + /* + case PA_SUBSCRIPTION_EVENT_SERVER: + switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + case PA_SUBSCRIPTION_EVENT_CHANGE: + // default sink changed but no one cares + break; + } + break; + */ + } + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Simple callback to check for success + */ +void pulseaudio::simple_callback(pa_context *context, int success, void *userdata) { + if (!success) + throw pulseaudio_error("Something failed: %s" + string{pa_strerror(pa_context_errno(context))}); + pulseaudio *This = static_cast(userdata); + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Callback when getting default sink name + */ +void pulseaudio::get_default_sink_callback(pa_context *context, const pa_server_info *info, void *userdata) { + pulseaudio *This = static_cast(userdata); + if (!info) { + throw pulseaudio_error("Failed to get server information: %s" + string{pa_strerror(pa_context_errno(context))}); + } else { + This->s_name = info->default_sink_name; + This->def_s_name = info->default_sink_name; + } + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Callback when getting sink info & existence + */ +void pulseaudio::sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { + (void) context; + pulseaudio *This = static_cast(userdata); + if (eol < 0) { + //throw pulseaudio_error("Failed to get server information: " + string{pa_strerror(pa_context_errno(context))}); + This->exists = false; + pa_threaded_mainloop_signal(This->m_mainloop, 0); + return; + } + if (eol) + return; + if (!info) { + This->exists = false; + } else { + This->exists = true; + This->s_index = info->index; + } + pa_threaded_mainloop_signal(This->m_mainloop, 0); +} + +/** + * Callback when context state changes + */ +void pulseaudio::context_state_callback(pa_context *context, void *userdata) { + pulseaudio* This = static_cast(userdata); + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(This->m_mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +POLYBAR_NS_END diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 1ce45df6e..37206ea4a 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -2,6 +2,7 @@ #include "adapters/alsa/control.hpp" #include "adapters/alsa/generic.hpp" #include "adapters/alsa/mixer.hpp" +#include "adapters/pulseaudio.hpp" #include "drawtypes/label.hpp" #include "drawtypes/progressbar.hpp" #include "drawtypes/ramp.hpp" @@ -20,6 +21,7 @@ namespace modules { volume_module::volume_module(const bar_settings& bar, string name_) : event_module(bar, move(name_)) { // Load configuration values + /* m_mapped = m_conf.get(name(), "mapped", m_mapped); auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s); @@ -66,6 +68,10 @@ namespace modules { } catch (const control_error& err) { throw module_error(err.what()); } + */ + auto sink_name = m_conf.get(name(), "sink", ""s); + //m_pulseaudio = new pulseaudio_t::element_type{move(sink_name)}; + m_pulseaudio = factory_util::unique(move(sink_name)); // Add formats and elements m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME}); @@ -87,13 +93,18 @@ namespace modules { } void volume_module::teardown() { + /* m_mixer.clear(); m_ctrl.clear(); + */ + //m_pulseaudio.clear(); + m_pulseaudio.reset(); snd_config_update_free_global(); } bool volume_module::has_event() { // Poll for mixer and control events + /* try { if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) { return true; @@ -110,12 +121,19 @@ namespace modules { } catch (const alsa_exception& e) { m_log.err("%s: %s", name(), e.what()); } - + */ + try { + if (m_pulseaudio->wait(25)) + return true; + } catch (const pulseaudio_error& e) { + m_log.err("%s: %s", name(), e.what()); + } return false; } bool volume_module::update() { // Consume pending events + /* if (m_mixer[mixer::MASTER]) { m_mixer[mixer::MASTER]->process_events(); } @@ -128,12 +146,15 @@ namespace modules { if (m_ctrl[control::HEADPHONE]) { m_ctrl[control::HEADPHONE]->process_events(); } + */ + m_pulseaudio->process_events(); // Get volume, mute and headphone state m_volume = 100; m_muted = false; m_headphones = false; + /* try { if (m_mixer[mixer::MASTER]) { m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f @@ -164,6 +185,15 @@ namespace modules { } catch (const alsa_exception& err) { m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what()); } + */ + try { + if (m_pulseaudio) { + m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f; + m_muted = m_muted || m_pulseaudio->is_muted(); + } + } catch (const pulseaudio_error& err) { + m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what()); + } // Replace label tokens if (m_label_volume) { @@ -222,11 +252,12 @@ namespace modules { return false; } else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) { return false; - } else if (!m_mixer[mixer::MASTER]) { - return false; + //} else if (!m_mixer[mixer::MASTER]) { + // return false; } try { + /* vector mixers; bool headphones{m_headphones}; @@ -266,6 +297,23 @@ namespace modules { mixer->process_events(); } } + */ + if (m_pulseaudio && !m_pulseaudio->get_name().empty()) { + if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) { + printf("toggling mute\n"); + m_pulseaudio->toggle_mute(); + } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) { + // cap above 100 (~150)? + m_pulseaudio->set_volume(math_util::cap(m_pulseaudio->get_volume() + 5, 0, 100)); + } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) { + m_pulseaudio->set_volume(math_util::cap(m_pulseaudio->get_volume() - 5, 0, 100)); + } else { + return false; + } + if (m_pulseaudio->wait(0)) { + m_pulseaudio->process_events(); + } + } } catch (const exception& err) { m_log.err("%s: Failed to handle command (%s)", name(), err.what()); } From 08c8f5f1b21c05c2ebbbca26145fe8a2b21f05cc Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Fri, 15 Sep 2017 20:51:22 -0700 Subject: [PATCH 02/16] fix(build): Only use one volume backend --- build.sh | 9 +++++++ doc/CMakeLists.txt | 2 +- include/modules/meta/factory.hpp | 4 +-- include/modules/unsupported.hpp | 4 +-- include/modules/volume.hpp | 18 +++++++++---- src/CMakeLists.txt | 4 ++- src/adapters/pulseaudio.cpp | 20 +++++++++++--- src/modules/volume.cpp | 45 ++++++++++++++++++++------------ 8 files changed, 74 insertions(+), 32 deletions(-) diff --git a/build.sh b/build.sh index b9b3ac45e..d65bd23f5 100755 --- a/build.sh +++ b/build.sh @@ -30,6 +30,7 @@ function main local build_ipc_msg="ON" local enable_alsa="ON" + local enable_pulseaudio="ON" local enable_i3="ON" local enable_network="ON" local enable_mpd="ON" @@ -41,6 +42,8 @@ function main [[ "${p^^}" != "Y" ]] && enable_i3="OFF" read -r -p "$(msg "Include support for \"internal/volume\" (requires alsalib) ---------- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_alsa="OFF" + read -r -p "$(msg "Include support for \"internal/volume\" (requires libpulse) --------- [Y/n]: ")" -n 1 p && echo + [[ "${p^^}" != "Y" ]] && enable_pulseaudio="OFF" read -r -p "$(msg "Include support for \"internal/network\" (requires wireless_tools) -- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_network="OFF" read -r -p "$(msg "Include support for \"internal/mpd\" (requires libmpdclient) -------- [Y/n]: ")" -n 1 p && echo @@ -50,6 +53,11 @@ function main read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages --------------------- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && build_ipc_msg="OFF" + # pulseaudio overrides alsa + if [[ "${enable_alsa}" == "ON" ]] && [[ "${enable_pulseaudio}" == "ON" ]]; then + enable_alsa="OFF" + fi + local cxx="c++" local cc="cc" @@ -68,6 +76,7 @@ function main -DCMAKE_C_COMPILER="${cc}" \ -DCMAKE_CXX_COMPILER="${cxx}" \ -DENABLE_ALSA:BOOL="${enable_alsa}" \ + -DENABLE_PULSEAUDIO:BOOL="${enable_pulseaudio}"\ -DENABLE_I3:BOOL="${enable_i3}" \ -DENABLE_MPD:BOOL="${enable_mpd}" \ -DENABLE_NETWORK:BOOL="${enable_network}" \ diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index e265ca5a4..b14d452c5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -16,7 +16,7 @@ queryfont(FONT_SIJI ${FONT_SIJI} FIELDS family pixelsize) # Strip disabled modules {{{ -if(NOT ENABLE_ALSA) +if(NOT (ENABLE_ALSA OR ENABLE_PULSEAUDIO)) string(REPLACE " volume" "" MODULES_RIGHT ${MODULES_RIGHT}) endif() if(NOT ENABLE_I3) diff --git a/include/modules/meta/factory.hpp b/include/modules/meta/factory.hpp index 9c6ca9d82..8e510c588 100644 --- a/include/modules/meta/factory.hpp +++ b/include/modules/meta/factory.hpp @@ -31,7 +31,7 @@ #if ENABLE_NETWORK #include "modules/network.hpp" #endif -#if ENABLE_ALSA +#if ENABLE_ALSA || ENABLE_PULSEAUDIO #include "modules/volume.hpp" #endif #if ENABLE_CURL @@ -40,7 +40,7 @@ #if ENABLE_XKEYBOARD #include "modules/xkeyboard.hpp" #endif -#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && ENABLE_XKEYBOARD) +#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && (ENABLE_ALSA || ENABLE_PULSEAUDIO) && ENABLE_CURL && ENABLE_XKEYBOARD) #include "modules/unsupported.hpp" #endif diff --git a/include/modules/unsupported.hpp b/include/modules/unsupported.hpp index c391a1c2d..f9c2746e7 100644 --- a/include/modules/unsupported.hpp +++ b/include/modules/unsupported.hpp @@ -1,5 +1,5 @@ #pragma once -#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && ENABLE_XKEYBOARD +#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && (ENABLE_ALSA || ENABLE_PULSEAUDIO) && ENABLE_CURL && ENABLE_XKEYBOARD #error "Support has been enabled for all optional modules" #endif @@ -40,7 +40,7 @@ namespace modules { #if not ENABLE_NETWORK DEFINE_UNSUPPORTED_MODULE(network_module, "internal/network"); #endif -#if not ENABLE_ALSA +#if not (ENABLE_ALSA || ENABLE_PULSEAUDIO) DEFINE_UNSUPPORTED_MODULE(volume_module, "internal/volume"); #endif #if not ENABLE_CURL diff --git a/include/modules/volume.hpp b/include/modules/volume.hpp index 92949e430..008e67720 100644 --- a/include/modules/volume.hpp +++ b/include/modules/volume.hpp @@ -1,26 +1,31 @@ #pragma once #include "settings.hpp" -#include "adapters/pulseaudio.hpp" #include "modules/meta/event_module.hpp" #include "modules/meta/input_handler.hpp" POLYBAR_NS // fwd +#if ENABLE_ALSA namespace alsa { class mixer; class control; } -//class pulseaudio; +#elif ENABLE_PULSEAUDIO +class pulseaudio; +#endif namespace modules { enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE }; enum class control { NONE = 0, HEADPHONE }; +#if ENABLE_ALSA using mixer_t = shared_ptr; using control_t = shared_ptr; +#elif ENABLE_PULSEAUDIO using pulseaudio_t = shared_ptr; +#endif class volume_module : public event_module, public input_handler { public: @@ -57,12 +62,15 @@ namespace modules { label_t m_label_volume; label_t m_label_muted; +#if ENABLE_PULSEAUDIO + pulseaudio_t m_pulseaudio; +#elif ENABLE_ALSA map m_mixer; map m_ctrl; - pulseaudio_t m_pulseaudio; - //int m_headphoneid{0}; - //bool m_mapped{false}; + int m_headphoneid{0}; + bool m_mapped{false}; +#endif atomic m_muted{false}; atomic m_headphones{false}; atomic m_volume{0}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 251c28669..36c892cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,7 +8,6 @@ file(GLOB_RECURSE files RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.c[p]*) list(REMOVE_ITEM files ipc.cpp) if(NOT ENABLE_ALSA) - list(REMOVE_ITEM files modules/volume.cpp) list(REMOVE_ITEM files adapters/alsa/control.cpp) list(REMOVE_ITEM files adapters/alsa/mixer.cpp) endif() @@ -31,6 +30,9 @@ endif() if(NOT ENABLE_PULSEAUDIO) list(REMOVE_ITEM files adapters/pulseaudio.cpp) endif() +if(NOT (ENABLE_ALSA OR ENABLE_PULSEAUDIO)) + list(REMOVE_ITEM files modules/volume.cpp) +endif() if(NOT WITH_XRANDR) list(REMOVE_ITEM files x11/extensions/randr.cpp) endif() diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 8af56cf85..6f96307e6 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -1,7 +1,6 @@ #include "adapters/pulseaudio.hpp" // TODO possibly move all the callback functions to lambda functions -// also maybe use pa_operation_unref(op) // create base volume backend class (mixer/control, pulseaudio inherits from base class) // use index instead of name internally? POLYBAR_NS @@ -56,6 +55,7 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); if (exists) s_name = sink_name; else { @@ -65,15 +65,20 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { } while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); s_name = def_s_name; // get the sink index op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); } op = pa_context_subscribe(m_context, static_cast(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER), simple_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); pa_context_set_subscribe_callback(m_context, subscribe_callback, this); pa_threaded_mainloop_unlock(m_mainloop); @@ -84,7 +89,6 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { * Deconstruct pulseaudio */ pulseaudio::~pulseaudio() { - pa_threaded_mainloop_unlock(m_mainloop); pa_threaded_mainloop_stop(m_mainloop); pa_context_disconnect(m_context); pa_context_unref(m_context); @@ -123,6 +127,7 @@ int pulseaudio::process_events() { o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); while (pa_operation_get_state(o) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(o); if (exists) s_name = spec_s_name; break; @@ -131,15 +136,18 @@ int pulseaudio::process_events() { o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); while (pa_operation_get_state(o) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(o); break; // get default sink case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); while (pa_operation_get_state(o) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(o); o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); while (pa_operation_get_state(o) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(o); break; } m_events.pop(); @@ -154,9 +162,9 @@ int pulseaudio::process_events() { int pulseaudio::get_volume() { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) { + while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); - } + pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop); // alternatively, user pa_cvolume_avg_mask() to average selected channels //return math_util::percentage(pa_cvolume_avg(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); @@ -172,11 +180,13 @@ void pulseaudio::set_volume(float percentage) { pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop); } @@ -188,6 +198,7 @@ void pulseaudio::set_mute(bool mode) { pa_operation *op = pa_context_set_sink_mute_by_name(m_context, s_name.c_str(), mode, simple_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop); } @@ -206,6 +217,7 @@ bool pulseaudio::is_muted() { pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), check_mute_callback, this); while (pa_operation_get_state(op) != PA_OPERATION_DONE) pa_threaded_mainloop_wait(m_mainloop); + pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop); return muted; } diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 37206ea4a..7a81b226c 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -1,8 +1,11 @@ #include "modules/volume.hpp" +#if ENABLE_ALSA #include "adapters/alsa/control.hpp" #include "adapters/alsa/generic.hpp" #include "adapters/alsa/mixer.hpp" +#elif ENABLE_PULSEAUDIO #include "adapters/pulseaudio.hpp" +#endif #include "drawtypes/label.hpp" #include "drawtypes/progressbar.hpp" #include "drawtypes/ramp.hpp" @@ -14,14 +17,16 @@ POLYBAR_NS +#if ENABLE_ALSA using namespace alsa; +#endif namespace modules { template class module; volume_module::volume_module(const bar_settings& bar, string name_) : event_module(bar, move(name_)) { // Load configuration values - /* +#if ENABLE_ALSA m_mapped = m_conf.get(name(), "mapped", m_mapped); auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s); @@ -68,10 +73,10 @@ namespace modules { } catch (const control_error& err) { throw module_error(err.what()); } - */ +#elif ENABLE_PULSEAUDIO auto sink_name = m_conf.get(name(), "sink", ""s); - //m_pulseaudio = new pulseaudio_t::element_type{move(sink_name)}; m_pulseaudio = factory_util::unique(move(sink_name)); +#endif // Add formats and elements m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME}); @@ -93,18 +98,18 @@ namespace modules { } void volume_module::teardown() { - /* +#if ENABLE_ALSA m_mixer.clear(); m_ctrl.clear(); - */ - //m_pulseaudio.clear(); - m_pulseaudio.reset(); snd_config_update_free_global(); +#elif ENABLE_PULSEAUDIO + m_pulseaudio.reset(); +#endif } bool volume_module::has_event() { // Poll for mixer and control events - /* +#if ENABLE_ALSA try { if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) { return true; @@ -121,19 +126,20 @@ namespace modules { } catch (const alsa_exception& e) { m_log.err("%s: %s", name(), e.what()); } - */ +#elif ENABLE_PULSEAUDIO try { if (m_pulseaudio->wait(25)) return true; } catch (const pulseaudio_error& e) { m_log.err("%s: %s", name(), e.what()); } +#endif return false; } bool volume_module::update() { // Consume pending events - /* +#if ENABLE_ALSA if (m_mixer[mixer::MASTER]) { m_mixer[mixer::MASTER]->process_events(); } @@ -146,15 +152,16 @@ namespace modules { if (m_ctrl[control::HEADPHONE]) { m_ctrl[control::HEADPHONE]->process_events(); } - */ +#elif ENABLE_PULSEAUDIO m_pulseaudio->process_events(); +#endif // Get volume, mute and headphone state m_volume = 100; m_muted = false; m_headphones = false; - /* +#if ENABLE_ALSA try { if (m_mixer[mixer::MASTER]) { m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f @@ -185,7 +192,7 @@ namespace modules { } catch (const alsa_exception& err) { m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what()); } - */ +#elif ENABLE_PULSEAUDIO try { if (m_pulseaudio) { m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f; @@ -194,6 +201,7 @@ namespace modules { } catch (const pulseaudio_error& err) { m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what()); } +#endif // Replace label tokens if (m_label_volume) { @@ -252,12 +260,14 @@ namespace modules { return false; } else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) { return false; - //} else if (!m_mixer[mixer::MASTER]) { - // return false; +#if ENABLE_ALSA + } else if (!m_mixer[mixer::MASTER]) { + return false; +#endif } try { - /* +#if ENABLE_ALSA vector mixers; bool headphones{m_headphones}; @@ -297,7 +307,7 @@ namespace modules { mixer->process_events(); } } - */ +#elif ENABLE_PULSEAUDIO if (m_pulseaudio && !m_pulseaudio->get_name().empty()) { if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) { printf("toggling mute\n"); @@ -314,6 +324,7 @@ namespace modules { m_pulseaudio->process_events(); } } +#endif } catch (const exception& err) { m_log.err("%s: Failed to handle command (%s)", name(), err.what()); } From 8a2b5ac94ac0da330f9bc51c7ac9a0b8e83186fd Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Fri, 15 Sep 2017 21:10:25 -0700 Subject: [PATCH 03/16] refactor(pulseaudio): Move loop to inline function --- include/adapters/pulseaudio.hpp | 2 ++ src/adapters/pulseaudio.cpp | 57 +++++++++++---------------------- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index fb7ff022d..d4ac38297 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -51,6 +51,8 @@ class pulseaudio { static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); static void context_state_callback(pa_context *context, void *userdata); + inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop); + // used for temporary callback results pa_cvolume cv; bool muted; diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 6f96307e6..4e51165e3 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -53,9 +53,7 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { } pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); if (exists) s_name = sink_name; else { @@ -63,22 +61,16 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { if (!op) { throw pulseaudio_error("Failed to get pulseaudio server info."); } - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); s_name = def_s_name; // get the sink index op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); } op = pa_context_subscribe(m_context, static_cast(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER), simple_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_context_set_subscribe_callback(m_context, subscribe_callback, this); pa_threaded_mainloop_unlock(m_mainloop); @@ -125,29 +117,21 @@ int pulseaudio::process_events() { // try to get specified sink case evtype::NEW: o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); - while (pa_operation_get_state(o) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(o); + wait_loop(o, m_mainloop); if (exists) s_name = spec_s_name; break; // get volume case evtype::CHANGE: o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); - while (pa_operation_get_state(o) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(o); + wait_loop(o, m_mainloop); break; // get default sink case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); - while (pa_operation_get_state(o) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(o); + wait_loop(o, m_mainloop); o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); - while (pa_operation_get_state(o) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(o); + wait_loop(o, m_mainloop); break; } m_events.pop(); @@ -162,9 +146,7 @@ int pulseaudio::process_events() { int pulseaudio::get_volume() { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); // alternatively, user pa_cvolume_avg_mask() to average selected channels //return math_util::percentage(pa_cvolume_avg(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); @@ -178,15 +160,11 @@ int pulseaudio::get_volume() { void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); } @@ -196,9 +174,7 @@ void pulseaudio::set_volume(float percentage) { void pulseaudio::set_mute(bool mode) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_set_sink_mute_by_name(m_context, s_name.c_str(), mode, simple_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); } @@ -215,9 +191,7 @@ void pulseaudio::toggle_mute() { bool pulseaudio::is_muted() { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), check_mute_callback, this); - while (pa_operation_get_state(op) != PA_OPERATION_DONE) - pa_threaded_mainloop_wait(m_mainloop); - pa_operation_unref(op); + wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); return muted; } @@ -364,4 +338,9 @@ void pulseaudio::context_state_callback(pa_context *context, void *userdata) { } } +inline void pulseaudio::wait_loop(pa_operation *op, pa_threaded_mainloop *loop) { + while (pa_operation_get_state(op) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(loop); + pa_operation_unref(op); +} POLYBAR_NS_END From ab2c960e0f3dd5c0c2119d27f7411fe791957f1e Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sat, 16 Sep 2017 14:49:46 -0700 Subject: [PATCH 04/16] fix(pulseaudio): Use inc_volume() instead of set_volume() --- include/adapters/pulseaudio.hpp | 1 + src/adapters/pulseaudio.cpp | 37 ++++++++++++++++++++------------- src/modules/volume.cpp | 4 ++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index d4ac38297..1e3e7f47e 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -38,6 +38,7 @@ class pulseaudio { int get_volume(); void set_volume(float percentage); + void inc_volume(int delta_perc); void set_mute(bool mode); void toggle_mute(); bool is_muted(); diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 4e51165e3..625794fa7 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -48,7 +48,11 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { pa_threaded_mainloop_wait(m_mainloop); if (pa_context_get_state(m_context) != PA_CONTEXT_READY) { - //goto error; + pa_threaded_mainloop_unlock(m_mainloop); + pa_threaded_mainloop_stop(m_mainloop); + pa_context_disconnect(m_context); + pa_context_unref(m_context); + pa_threaded_mainloop_free(m_mainloop); throw pulseaudio_error("Could not connect to pulseaudio server."); } @@ -68,8 +72,7 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { wait_loop(op, m_mainloop); } - op = pa_context_subscribe(m_context, static_cast(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER), - simple_callback, this); + op = pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_SINK, simple_callback, this); wait_loop(op, m_mainloop); pa_context_set_subscribe_callback(m_context, subscribe_callback, this); @@ -149,14 +152,12 @@ int pulseaudio::get_volume() { wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); // alternatively, user pa_cvolume_avg_mask() to average selected channels - //return math_util::percentage(pa_cvolume_avg(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); return math_util::percentage(pa_cvolume_max(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); } /** * Set volume to given percentage */ -//void pulseaudio::set_volume(int delta_perc) { void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); @@ -168,6 +169,23 @@ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_unlock(m_mainloop); } +/** + * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume) + */ +void pulseaudio::inc_volume(int delta_perc) { + pa_threaded_mainloop_lock(m_mainloop); + pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + wait_loop(op, m_mainloop); + pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_MUTED, PA_VOLUME_NORM); + if (delta_perc > 0) + pa_cvolume_inc(&cv, vol); + else + pa_cvolume_dec(&cv, vol); + op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); + wait_loop(op, m_mainloop); + pa_threaded_mainloop_unlock(m_mainloop); +} + /** * Set mute state */ @@ -258,15 +276,6 @@ void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_t break; } break; - /* - case PA_SUBSCRIPTION_EVENT_SERVER: - switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { - case PA_SUBSCRIPTION_EVENT_CHANGE: - // default sink changed but no one cares - break; - } - break; - */ } pa_threaded_mainloop_signal(This->m_mainloop, 0); } diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 7a81b226c..58d94df58 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -314,9 +314,9 @@ namespace modules { m_pulseaudio->toggle_mute(); } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) { // cap above 100 (~150)? - m_pulseaudio->set_volume(math_util::cap(m_pulseaudio->get_volume() + 5, 0, 100)); + m_pulseaudio->inc_volume(5); } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) { - m_pulseaudio->set_volume(math_util::cap(m_pulseaudio->get_volume() - 5, 0, 100)); + m_pulseaudio->inc_volume(-5); } else { return false; } From ef0fb44f8ac2a5f2db8139d8ad9f3ee0d3357def Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sun, 17 Sep 2017 16:46:51 -0700 Subject: [PATCH 05/16] refactor(pulseaudio): Use index instead of name to track sinks --- include/adapters/pulseaudio.hpp | 4 +-- src/adapters/pulseaudio.cpp | 62 +++++++++++++-------------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 1e3e7f47e..28411b4cb 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -66,11 +66,9 @@ class pulseaudio { // specified sink name string spec_s_name; - // sink currently in use - string s_name; // default sink name string def_s_name; - uint32_t s_index{0}; + uint32_t m_index{0}; }; POLYBAR_NS_END diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 625794fa7..0a3601d75 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -2,7 +2,6 @@ // TODO possibly move all the callback functions to lambda functions // create base volume backend class (mixer/control, pulseaudio inherits from base class) -// use index instead of name internally? POLYBAR_NS /* Multichannel volumes: @@ -58,17 +57,14 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); - if (exists) - s_name = sink_name; - else { + if (!exists) { op = pa_context_get_server_info(m_context, get_default_sink_callback, this); if (!op) { throw pulseaudio_error("Failed to get pulseaudio server info."); } wait_loop(op, m_mainloop); - s_name = def_s_name; // get the sink index - op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); + op = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); } @@ -95,7 +91,7 @@ pulseaudio::~pulseaudio() { * Get sink name */ const string& pulseaudio::get_name() { - return s_name; + return spec_s_name; } /** @@ -119,21 +115,20 @@ int pulseaudio::process_events() { switch (m_events.front()) { // try to get specified sink case evtype::NEW: + // redundant if already using specified sink o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); wait_loop(o, m_mainloop); - if (exists) - s_name = spec_s_name; break; // get volume case evtype::CHANGE: - o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(o, m_mainloop); break; // get default sink case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); wait_loop(o, m_mainloop); - o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this); + o = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(o, m_mainloop); break; } @@ -148,11 +143,11 @@ int pulseaudio::process_events() { */ int pulseaudio::get_volume() { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); // alternatively, user pa_cvolume_avg_mask() to average selected channels - return math_util::percentage(pa_cvolume_max(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM); + return static_cast(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f); } /** @@ -160,11 +155,11 @@ int pulseaudio::get_volume() { */ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); - op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); + op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); } @@ -173,15 +168,16 @@ void pulseaudio::set_volume(float percentage) { * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume) */ void pulseaudio::inc_volume(int delta_perc) { + // set max value? pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this); + pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); - pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_MUTED, PA_VOLUME_NORM); + pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_NORM); if (delta_perc > 0) pa_cvolume_inc(&cv, vol); else pa_cvolume_dec(&cv, vol); - op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this); + op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); } @@ -191,7 +187,7 @@ void pulseaudio::inc_volume(int delta_perc) { */ void pulseaudio::set_mute(bool mode) { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_set_sink_mute_by_name(m_context, s_name.c_str(), mode, simple_callback, this); + pa_operation *op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); } @@ -208,7 +204,7 @@ void pulseaudio::toggle_mute() { */ bool pulseaudio::is_muted() { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), check_mute_callback, this); + pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, check_mute_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); return muted; @@ -250,26 +246,26 @@ void pulseaudio::get_sink_volume_callback(pa_context *context, const pa_sink_inf */ void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { pulseaudio *This = static_cast(userdata); + if (idx == PA_INVALID_INDEX) + throw pulseaudio_error("Invalid index given: " + string{pa_strerror(pa_context_errno(context))}); switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK: switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { case PA_SUBSCRIPTION_EVENT_NEW: // if using the default sink, check if the new sink matches our specified sink - if (This->s_name == This->def_s_name && This->spec_s_name != This->def_s_name) { + //if (This->s_name == This->def_s_name && This->spec_s_name != This->def_s_name) { printf("NEW\n"); This->m_events.emplace(evtype::NEW); - } + //} break; case PA_SUBSCRIPTION_EVENT_CHANGE: - if (idx == PA_INVALID_INDEX) { - throw pulseaudio_error("Invalid index given: " + string{pa_strerror(pa_context_errno(context))}); - } else if (idx == This->s_index) { + if (idx == This->m_index) { printf("CHANGE\n"); This->m_events.emplace(evtype::CHANGE); } break; case PA_SUBSCRIPTION_EVENT_REMOVE: - if (idx == This->s_index) { + if (idx == This->m_index) { printf("REMOVE\n"); This->m_events.emplace(evtype::REMOVE); } @@ -298,7 +294,6 @@ void pulseaudio::get_default_sink_callback(pa_context *context, const pa_server_ if (!info) { throw pulseaudio_error("Failed to get server information: %s" + string{pa_strerror(pa_context_errno(context))}); } else { - This->s_name = info->default_sink_name; This->def_s_name = info->default_sink_name; } pa_threaded_mainloop_signal(This->m_mainloop, 0); @@ -310,19 +305,11 @@ void pulseaudio::get_default_sink_callback(pa_context *context, const pa_server_ void pulseaudio::sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { (void) context; pulseaudio *This = static_cast(userdata); - if (eol < 0) { - //throw pulseaudio_error("Failed to get server information: " + string{pa_strerror(pa_context_errno(context))}); - This->exists = false; - pa_threaded_mainloop_signal(This->m_mainloop, 0); - return; - } - if (eol) - return; - if (!info) { + if (eol || !info) { This->exists = false; } else { This->exists = true; - This->s_index = info->index; + This->m_index = info->index; } pa_threaded_mainloop_signal(This->m_mainloop, 0); } @@ -352,4 +339,5 @@ inline void pulseaudio::wait_loop(pa_operation *op, pa_threaded_mainloop *loop) pa_threaded_mainloop_wait(loop); pa_operation_unref(op); } + POLYBAR_NS_END From 078cf1a970711a884736e54bb2287b206da5ef29 Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Wed, 20 Sep 2017 21:59:38 -0700 Subject: [PATCH 06/16] fix(pulseaudio): Rewrite error checking --- include/adapters/pulseaudio.hpp | 1 + src/adapters/pulseaudio.cpp | 79 ++++++++++++++------------------- src/modules/volume.cpp | 8 +++- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 28411b4cb..982e928d0 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -55,6 +55,7 @@ class pulseaudio { inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop); // used for temporary callback results + int success; pa_cvolume cv; bool muted; bool exists; diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 0a3601d75..51e79d2c5 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -1,15 +1,7 @@ #include "adapters/pulseaudio.hpp" -// TODO possibly move all the callback functions to lambda functions -// create base volume backend class (mixer/control, pulseaudio inherits from base class) POLYBAR_NS -/* Multichannel volumes: - * use pa_cvolume_max(), and pa_cvolume_scale() - * - * see https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/WritingVolumeControlUIs/ - */ - /** * Construct pulseaudio object */ @@ -63,6 +55,8 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { throw pulseaudio_error("Failed to get pulseaudio server info."); } wait_loop(op, m_mainloop); + if (def_s_name.empty()) + throw pulseaudio_error("Failed to get default sink."); // get the sink index op = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); @@ -70,6 +64,8 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { op = pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_SINK, simple_callback, this); wait_loop(op, m_mainloop); + if (!success) + throw pulseaudio_error("Failed to subscribe to server."); pa_context_set_subscribe_callback(m_context, subscribe_callback, this); pa_threaded_mainloop_unlock(m_mainloop); @@ -121,6 +117,7 @@ int pulseaudio::process_events() { break; // get volume case evtype::CHANGE: + // doesn't do anything o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(o, m_mainloop); break; @@ -128,6 +125,8 @@ int pulseaudio::process_events() { case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); wait_loop(o, m_mainloop); + if (def_s_name.empty()) + throw pulseaudio_error("Failed to get default sink."); o = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(o, m_mainloop); break; @@ -146,6 +145,8 @@ int pulseaudio::get_volume() { pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); + //if (!pa_cvolume_valid(&cv)) + // throw pulseaudio_error("Failed to get volume."); // alternatively, user pa_cvolume_avg_mask() to average selected channels return static_cast(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f); } @@ -157,10 +158,14 @@ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); + //if (!pa_cvolume_valid(&cv)) + // throw pulseaudio_error("Failed to get volume."); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); + if (!success) + throw pulseaudio_error("Failed to set sink volume."); pa_threaded_mainloop_unlock(m_mainloop); } @@ -172,6 +177,8 @@ void pulseaudio::inc_volume(int delta_perc) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); + //if (!pa_cvolume_valid(&cv)) + // throw pulseaudio_error("Failed to get volume."); pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_NORM); if (delta_perc > 0) pa_cvolume_inc(&cv, vol); @@ -179,6 +186,8 @@ void pulseaudio::inc_volume(int delta_perc) { pa_cvolume_dec(&cv, vol); op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); + if (!success) + throw pulseaudio_error("Failed to set sink volume."); pa_threaded_mainloop_unlock(m_mainloop); } @@ -189,6 +198,8 @@ void pulseaudio::set_mute(bool mode) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this); wait_loop(op, m_mainloop); + if (!success) + throw pulseaudio_error("Failed to mute sink."); pa_threaded_mainloop_unlock(m_mainloop); } @@ -213,12 +224,7 @@ bool pulseaudio::is_muted() { /** * Callback when getting current mute state */ -void pulseaudio::check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { - if (eol < 0) { - throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))}); - } - if (eol) - return; +void pulseaudio::check_mute_callback(pa_context *, const pa_sink_info *info, int, void *userdata) { pulseaudio* This = static_cast(userdata); if (info) This->muted = info->mute; @@ -228,13 +234,7 @@ void pulseaudio::check_mute_callback(pa_context *context, const pa_sink_info *in /** * Callback when getting volume */ -void pulseaudio::get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { - if (eol < 0) { - throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))}); - } - if (eol) - return; - //pa_assert(info); +void pulseaudio::get_sink_volume_callback(pa_context *, const pa_sink_info *info, int, void *userdata) { pulseaudio* This = static_cast(userdata); if (info) This->cv = info->volume; @@ -244,31 +244,23 @@ void pulseaudio::get_sink_volume_callback(pa_context *context, const pa_sink_inf /** * Callback when subscribing to changes */ -void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { +void pulseaudio::subscribe_callback(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { pulseaudio *This = static_cast(userdata); if (idx == PA_INVALID_INDEX) - throw pulseaudio_error("Invalid index given: " + string{pa_strerror(pa_context_errno(context))}); + return; switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK: switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { case PA_SUBSCRIPTION_EVENT_NEW: - // if using the default sink, check if the new sink matches our specified sink - //if (This->s_name == This->def_s_name && This->spec_s_name != This->def_s_name) { - printf("NEW\n"); This->m_events.emplace(evtype::NEW); - //} break; case PA_SUBSCRIPTION_EVENT_CHANGE: - if (idx == This->m_index) { - printf("CHANGE\n"); + if (idx == This->m_index) This->m_events.emplace(evtype::CHANGE); - } break; case PA_SUBSCRIPTION_EVENT_REMOVE: - if (idx == This->m_index) { - printf("REMOVE\n"); + if (idx == This->m_index) This->m_events.emplace(evtype::REMOVE); - } break; } break; @@ -279,35 +271,32 @@ void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_t /** * Simple callback to check for success */ -void pulseaudio::simple_callback(pa_context *context, int success, void *userdata) { - if (!success) - throw pulseaudio_error("Something failed: %s" + string{pa_strerror(pa_context_errno(context))}); +void pulseaudio::simple_callback(pa_context *, int success, void *userdata) { pulseaudio *This = static_cast(userdata); + This->success = success; pa_threaded_mainloop_signal(This->m_mainloop, 0); } /** * Callback when getting default sink name */ -void pulseaudio::get_default_sink_callback(pa_context *context, const pa_server_info *info, void *userdata) { +void pulseaudio::get_default_sink_callback(pa_context *, const pa_server_info *info, void *userdata) { pulseaudio *This = static_cast(userdata); - if (!info) { - throw pulseaudio_error("Failed to get server information: %s" + string{pa_strerror(pa_context_errno(context))}); - } else { + if (info->default_sink_name) This->def_s_name = info->default_sink_name; - } + else + This->def_s_name = ""s; pa_threaded_mainloop_signal(This->m_mainloop, 0); } /** * Callback when getting sink info & existence */ -void pulseaudio::sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) { - (void) context; +void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int eol, void *userdata) { pulseaudio *This = static_cast(userdata); - if (eol || !info) { + if (eol || !info) This->exists = false; - } else { + else { This->exists = true; This->m_index = info->index; } diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 58d94df58..409ce0575 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -75,7 +75,12 @@ namespace modules { } #elif ENABLE_PULSEAUDIO auto sink_name = m_conf.get(name(), "sink", ""s); - m_pulseaudio = factory_util::unique(move(sink_name)); + try { + m_pulseaudio = factory_util::unique(move(sink_name)); + //} catch (const pulseaudio_error& err) { + } catch (const pulseaudio_error& err) { + throw module_error(err.what()); + } #endif // Add formats and elements @@ -310,7 +315,6 @@ namespace modules { #elif ENABLE_PULSEAUDIO if (m_pulseaudio && !m_pulseaudio->get_name().empty()) { if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) { - printf("toggling mute\n"); m_pulseaudio->toggle_mute(); } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) { // cap above 100 (~150)? From 6dcb93f92de7103b6801c80c4cea277f2caad2ef Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Mon, 25 Sep 2017 16:42:42 -0700 Subject: [PATCH 07/16] fix(pulseaudio): fix get_name(), cleanup --- include/adapters/pulseaudio.hpp | 11 ++++++----- src/adapters/pulseaudio.cpp | 27 ++++++++++++++------------- src/modules/volume.cpp | 1 - 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 982e928d0..78c87dfe3 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -55,10 +55,12 @@ class pulseaudio { inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop); // used for temporary callback results - int success; + int success{0}; pa_cvolume cv; - bool muted; - bool exists; + bool muted{false}; + bool exists{false}; + // default sink name + string def_s_name; pa_context* m_context{nullptr}; pa_threaded_mainloop* m_mainloop{nullptr}; @@ -67,8 +69,7 @@ class pulseaudio { // specified sink name string spec_s_name; - // default sink name - string def_s_name; + string s_name; uint32_t m_index{0}; }; diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 51e79d2c5..2e613d094 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -47,8 +47,11 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { throw pulseaudio_error("Could not connect to pulseaudio server."); } - pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); - wait_loop(op, m_mainloop); + pa_operation *op{nullptr}; + if (!sink_name.empty()) { + op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); + wait_loop(op, m_mainloop); + } if (!exists) { op = pa_context_get_server_info(m_context, get_default_sink_callback, this); if (!op) { @@ -87,7 +90,7 @@ pulseaudio::~pulseaudio() { * Get sink name */ const string& pulseaudio::get_name() { - return spec_s_name; + return s_name; } /** @@ -112,8 +115,10 @@ int pulseaudio::process_events() { // try to get specified sink case evtype::NEW: // redundant if already using specified sink - o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); - wait_loop(o, m_mainloop); + if (!spec_s_name.empty()) { + o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); + wait_loop(o, m_mainloop); + } break; // get volume case evtype::CHANGE: @@ -145,8 +150,6 @@ int pulseaudio::get_volume() { pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); pa_threaded_mainloop_unlock(m_mainloop); - //if (!pa_cvolume_valid(&cv)) - // throw pulseaudio_error("Failed to get volume."); // alternatively, user pa_cvolume_avg_mask() to average selected channels return static_cast(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f); } @@ -158,8 +161,6 @@ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); - //if (!pa_cvolume_valid(&cv)) - // throw pulseaudio_error("Failed to get volume."); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); @@ -177,8 +178,6 @@ void pulseaudio::inc_volume(int delta_perc) { pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); - //if (!pa_cvolume_valid(&cv)) - // throw pulseaudio_error("Failed to get volume."); pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_NORM); if (delta_perc > 0) pa_cvolume_inc(&cv, vol); @@ -282,9 +281,10 @@ void pulseaudio::simple_callback(pa_context *, int success, void *userdata) { */ void pulseaudio::get_default_sink_callback(pa_context *, const pa_server_info *info, void *userdata) { pulseaudio *This = static_cast(userdata); - if (info->default_sink_name) + if (info->default_sink_name) { This->def_s_name = info->default_sink_name; - else + This->s_name = info->default_sink_name; + } else This->def_s_name = ""s; pa_threaded_mainloop_signal(This->m_mainloop, 0); } @@ -299,6 +299,7 @@ void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int else { This->exists = true; This->m_index = info->index; + This->s_name = info->name; } pa_threaded_mainloop_signal(This->m_mainloop, 0); } diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 409ce0575..0fdf7101e 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -77,7 +77,6 @@ namespace modules { auto sink_name = m_conf.get(name(), "sink", ""s); try { m_pulseaudio = factory_util::unique(move(sink_name)); - //} catch (const pulseaudio_error& err) { } catch (const pulseaudio_error& err) { throw module_error(err.what()); } From 569d409e491b63a5acb2662504e54cfee93a157c Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Mon, 25 Sep 2017 19:24:19 -0700 Subject: [PATCH 08/16] fix(build): Check for pulseaudio binary --- build.sh | 5 ----- cmake/02-opts.cmake | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index d65bd23f5..a84530f81 100755 --- a/build.sh +++ b/build.sh @@ -53,11 +53,6 @@ function main read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages --------------------- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && build_ipc_msg="OFF" - # pulseaudio overrides alsa - if [[ "${enable_alsa}" == "ON" ]] && [[ "${enable_pulseaudio}" == "ON" ]]; then - enable_alsa="OFF" - fi - local cxx="c++" local cc="cc" diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake index 535ff7825..7ffdccbbf 100644 --- a/cmake/02-opts.cmake +++ b/cmake/02-opts.cmake @@ -8,6 +8,7 @@ checklib(ENABLE_I3 "binary" i3) checklib(ENABLE_MPD "pkg-config" libmpdclient) checklib(ENABLE_NETWORK "cmake" Libiw) checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse) +checklib(ENABLE_PULSEAUDIO "binary" pulseaudio) checklib(WITH_XRM "pkg-config" xcb-xrm) checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12") checklib(WITH_XCURSOR "pkg-config" "xcb-cursor") @@ -30,6 +31,9 @@ option(ENABLE_MPD "Enable mpd support" ON) option(ENABLE_NETWORK "Enable network support" ON) option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON) option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON) +if(ENABLE_PULSEAUDIO) + set(ENABLE_ALSA OFF) +endif() option(WITH_XRANDR "xcb-randr support" ON) option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON) From 077e24c660063afc975d1c5032cca5f976d574ef Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Mon, 25 Sep 2017 19:42:09 -0700 Subject: [PATCH 09/16] fix(travis): Add libpulse-dev --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d5a311307..08783c04f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ addons: - gcc-5 - i3-wm - libasound2-dev + - libpulse-dev - libcairo2-dev - libiw-dev - libmpdclient-dev From 570469329afc6727437fdfa70979772d6e2bdfe6 Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Tue, 26 Sep 2017 15:21:23 -0700 Subject: [PATCH 10/16] fix(pulseaudio): Move pulseaudio to separate module --- build.sh | 2 +- cmake/02-opts.cmake | 3 - doc/CMakeLists.txt | 2 +- include/modules/meta/factory.hpp | 9 +- include/modules/pulseaudio.hpp | 58 +++++++++++ include/modules/unsupported.hpp | 7 +- include/modules/volume.hpp | 13 --- src/CMakeLists.txt | 5 +- src/modules/pulseaudio.cpp | 161 +++++++++++++++++++++++++++++++ src/modules/volume.cpp | 64 +----------- 10 files changed, 236 insertions(+), 88 deletions(-) create mode 100644 include/modules/pulseaudio.hpp create mode 100644 src/modules/pulseaudio.cpp diff --git a/build.sh b/build.sh index a84530f81..3c8bae0ac 100755 --- a/build.sh +++ b/build.sh @@ -42,7 +42,7 @@ function main [[ "${p^^}" != "Y" ]] && enable_i3="OFF" read -r -p "$(msg "Include support for \"internal/volume\" (requires alsalib) ---------- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_alsa="OFF" - read -r -p "$(msg "Include support for \"internal/volume\" (requires libpulse) --------- [Y/n]: ")" -n 1 p && echo + read -r -p "$(msg "Include support for \"internal/pulseaudio\" (requires libpulse) ----- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_pulseaudio="OFF" read -r -p "$(msg "Include support for \"internal/network\" (requires wireless_tools) -- [Y/n]: ")" -n 1 p && echo [[ "${p^^}" != "Y" ]] && enable_network="OFF" diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake index 7ffdccbbf..557e9a148 100644 --- a/cmake/02-opts.cmake +++ b/cmake/02-opts.cmake @@ -31,9 +31,6 @@ option(ENABLE_MPD "Enable mpd support" ON) option(ENABLE_NETWORK "Enable network support" ON) option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON) option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON) -if(ENABLE_PULSEAUDIO) - set(ENABLE_ALSA OFF) -endif() option(WITH_XRANDR "xcb-randr support" ON) option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index b14d452c5..e265ca5a4 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -16,7 +16,7 @@ queryfont(FONT_SIJI ${FONT_SIJI} FIELDS family pixelsize) # Strip disabled modules {{{ -if(NOT (ENABLE_ALSA OR ENABLE_PULSEAUDIO)) +if(NOT ENABLE_ALSA) string(REPLACE " volume" "" MODULES_RIGHT ${MODULES_RIGHT}) endif() if(NOT ENABLE_I3) diff --git a/include/modules/meta/factory.hpp b/include/modules/meta/factory.hpp index 8e510c588..bd94482e5 100644 --- a/include/modules/meta/factory.hpp +++ b/include/modules/meta/factory.hpp @@ -31,16 +31,19 @@ #if ENABLE_NETWORK #include "modules/network.hpp" #endif -#if ENABLE_ALSA || ENABLE_PULSEAUDIO +#if ENABLE_ALSA #include "modules/volume.hpp" #endif +#if ENABLE_PULSEAUDIO +#include "modules/pulseaudio.hpp" +#endif #if ENABLE_CURL #include "modules/github.hpp" #endif #if ENABLE_XKEYBOARD #include "modules/xkeyboard.hpp" #endif -#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && (ENABLE_ALSA || ENABLE_PULSEAUDIO) && ENABLE_CURL && ENABLE_XKEYBOARD) +#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_PULSEAUDIO && ENABLE_CURL && ENABLE_XKEYBOARD) #include "modules/unsupported.hpp" #endif @@ -74,6 +77,8 @@ namespace { return new mpd_module(bar, move(module_name)); } else if (name == "internal/volume") { return new volume_module(bar, move(module_name)); + } else if (name == "internal/pulseaudio") { + return new pulseaudio_module(bar, move(module_name)); } else if (name == "internal/network") { return new network_module(bar, move(module_name)); #if DEBUG diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp new file mode 100644 index 000000000..a2598086e --- /dev/null +++ b/include/modules/pulseaudio.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "settings.hpp" +#include "modules/meta/event_module.hpp" +#include "modules/meta/input_handler.hpp" + +POLYBAR_NS + +// fwd +class pulseaudio; + +namespace modules { + using pulseaudio_t = shared_ptr; + + class pulseaudio_module : public event_module, public input_handler { + public: + explicit pulseaudio_module(const bar_settings&, string); + + void teardown(); + bool has_event(); + bool update(); + string get_format() const; + string get_output(); + bool build(builder* builder, const string& tag) const; + + protected: + bool input(string&& cmd); + + private: + static constexpr auto FORMAT_VOLUME = "format-volume"; + static constexpr auto FORMAT_MUTED = "format-muted"; + + static constexpr auto TAG_RAMP_VOLUME = ""; + static constexpr auto TAG_RAMP_HEADPHONES = ""; + static constexpr auto TAG_BAR_VOLUME = ""; + static constexpr auto TAG_LABEL_VOLUME = ""; + static constexpr auto TAG_LABEL_MUTED = ""; + + static constexpr auto EVENT_PREFIX = "vol"; + static constexpr auto EVENT_VOLUME_UP = "volup"; + static constexpr auto EVENT_VOLUME_DOWN = "voldown"; + static constexpr auto EVENT_TOGGLE_MUTE = "volmute"; + + progressbar_t m_bar_volume; + ramp_t m_ramp_volume; + ramp_t m_ramp_headphones; + label_t m_label_volume; + label_t m_label_muted; + + pulseaudio_t m_pulseaudio; + + atomic m_muted{false}; + atomic m_headphones{false}; + atomic m_volume{0}; + }; +} + +POLYBAR_NS_END diff --git a/include/modules/unsupported.hpp b/include/modules/unsupported.hpp index f9c2746e7..398229004 100644 --- a/include/modules/unsupported.hpp +++ b/include/modules/unsupported.hpp @@ -1,5 +1,5 @@ #pragma once -#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && (ENABLE_ALSA || ENABLE_PULSEAUDIO) && ENABLE_CURL && ENABLE_XKEYBOARD +#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_PULSEAUDIO && ENABLE_CURL && ENABLE_XKEYBOARD #error "Support has been enabled for all optional modules" #endif @@ -40,9 +40,12 @@ namespace modules { #if not ENABLE_NETWORK DEFINE_UNSUPPORTED_MODULE(network_module, "internal/network"); #endif -#if not (ENABLE_ALSA || ENABLE_PULSEAUDIO) +#if not ENABLE_ALSA DEFINE_UNSUPPORTED_MODULE(volume_module, "internal/volume"); #endif +#if not ENABLE_PULSEAUDIO + DEFINE_UNSUPPORTED_MODULE(pulseaudio_module, "internal/pulseaudio"); +#endif #if not ENABLE_CURL DEFINE_UNSUPPORTED_MODULE(github_module, "internal/github"); #endif diff --git a/include/modules/volume.hpp b/include/modules/volume.hpp index 008e67720..8c86665f2 100644 --- a/include/modules/volume.hpp +++ b/include/modules/volume.hpp @@ -7,25 +7,17 @@ POLYBAR_NS // fwd -#if ENABLE_ALSA namespace alsa { class mixer; class control; } -#elif ENABLE_PULSEAUDIO -class pulseaudio; -#endif namespace modules { enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE }; enum class control { NONE = 0, HEADPHONE }; -#if ENABLE_ALSA using mixer_t = shared_ptr; using control_t = shared_ptr; -#elif ENABLE_PULSEAUDIO - using pulseaudio_t = shared_ptr; -#endif class volume_module : public event_module, public input_handler { public: @@ -62,15 +54,10 @@ namespace modules { label_t m_label_volume; label_t m_label_muted; -#if ENABLE_PULSEAUDIO - pulseaudio_t m_pulseaudio; -#elif ENABLE_ALSA map m_mixer; map m_ctrl; - int m_headphoneid{0}; bool m_mapped{false}; -#endif atomic m_muted{false}; atomic m_headphones{false}; atomic m_volume{0}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 36c892cf5..f49aa88d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ file(GLOB_RECURSE files RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.c[p]*) list(REMOVE_ITEM files ipc.cpp) if(NOT ENABLE_ALSA) + list(REMOVE_ITEM files modules/volume.cpp) list(REMOVE_ITEM files adapters/alsa/control.cpp) list(REMOVE_ITEM files adapters/alsa/mixer.cpp) endif() @@ -28,11 +29,9 @@ if(NOT ENABLE_I3) list(REMOVE_ITEM files utils/i3.cpp) endif() if(NOT ENABLE_PULSEAUDIO) + list(REMOVE_ITEM files modules/pulseaudio.cpp) list(REMOVE_ITEM files adapters/pulseaudio.cpp) endif() -if(NOT (ENABLE_ALSA OR ENABLE_PULSEAUDIO)) - list(REMOVE_ITEM files modules/volume.cpp) -endif() if(NOT WITH_XRANDR) list(REMOVE_ITEM files x11/extensions/randr.cpp) endif() diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp new file mode 100644 index 000000000..037567b9f --- /dev/null +++ b/src/modules/pulseaudio.cpp @@ -0,0 +1,161 @@ +#include "modules/pulseaudio.hpp" +#include "adapters/pulseaudio.hpp" +#include "drawtypes/label.hpp" +#include "drawtypes/progressbar.hpp" +#include "drawtypes/ramp.hpp" +#include "utils/math.hpp" + +#include "modules/meta/base.inl" + +#include "settings.hpp" + +POLYBAR_NS + +namespace modules { + template class module; + + pulseaudio_module::pulseaudio_module(const bar_settings& bar, string name_) : event_module(bar, move(name_)) { + // Load configuration values + auto sink_name = m_conf.get(name(), "sink", ""s); + try { + m_pulseaudio = factory_util::unique(move(sink_name)); + } catch (const pulseaudio_error& err) { + throw module_error(err.what()); + } + + // Add formats and elements + m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME}); + m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME}); + + if (m_formatter->has(TAG_BAR_VOLUME)) { + m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME); + } + if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) { + m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%"); + } + if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) { + m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%"); + } + if (m_formatter->has(TAG_RAMP_VOLUME)) { + m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME); + m_ramp_headphones = load_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false); + } + } + + void pulseaudio_module::teardown() { + m_pulseaudio.reset(); + } + + bool pulseaudio_module::has_event() { + // Poll for mixer and control events + try { + if (m_pulseaudio->wait(25)) + return true; + } catch (const pulseaudio_error& e) { + m_log.err("%s: %s", name(), e.what()); + } + return false; + } + + bool pulseaudio_module::update() { + // Consume pending events + m_pulseaudio->process_events(); + + // Get volume, mute and headphone state + m_volume = 100; + m_muted = false; + m_headphones = false; + + try { + if (m_pulseaudio) { + m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f; + m_muted = m_muted || m_pulseaudio->is_muted(); + } + } catch (const pulseaudio_error& err) { + m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what()); + } + + // Replace label tokens + if (m_label_volume) { + m_label_volume->reset_tokens(); + m_label_volume->replace_token("%percentage%", to_string(m_volume)); + } + + if (m_label_muted) { + m_label_muted->reset_tokens(); + m_label_muted->replace_token("%percentage%", to_string(m_volume)); + } + + return true; + } + + string pulseaudio_module::get_format() const { + return m_muted ? FORMAT_MUTED : FORMAT_VOLUME; + } + + string pulseaudio_module::get_output() { + // Get the module output early so that + // the format prefix/suffix also gets wrapper + // with the cmd handlers + string output{module::get_output()}; + + if (m_handle_events) { + m_builder->cmd(mousebtn::LEFT, EVENT_TOGGLE_MUTE); + m_builder->cmd(mousebtn::SCROLL_UP, EVENT_VOLUME_UP); + m_builder->cmd(mousebtn::SCROLL_DOWN, EVENT_VOLUME_DOWN); + } + + m_builder->append(output); + + return m_builder->flush(); + } + + bool pulseaudio_module::build(builder* builder, const string& tag) const { + if (tag == TAG_BAR_VOLUME) { + builder->node(m_bar_volume->output(m_volume)); + } else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones)) { + builder->node(m_ramp_volume->get_by_percentage(m_volume)); + } else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones) { + builder->node(m_ramp_headphones->get_by_percentage(m_volume)); + } else if (tag == TAG_LABEL_VOLUME) { + builder->node(m_label_volume); + } else if (tag == TAG_LABEL_MUTED) { + builder->node(m_label_muted); + } else { + return false; + } + return true; + } + + bool pulseaudio_module::input(string&& cmd) { + if (!m_handle_events) { + return false; + } else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) { + return false; + } + + try { + if (m_pulseaudio && !m_pulseaudio->get_name().empty()) { + if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) { + m_pulseaudio->toggle_mute(); + } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) { + // cap above 100 (~150)? + m_pulseaudio->inc_volume(5); + } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) { + m_pulseaudio->inc_volume(-5); + } else { + return false; + } + if (m_pulseaudio->wait(0)) { + m_pulseaudio->process_events(); + } + } + } catch (const exception& err) { + m_log.err("%s: Failed to handle command (%s)", name(), err.what()); + } + + return true; + } +} + +POLYBAR_NS_END diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index 0fdf7101e..1ce45df6e 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -1,11 +1,7 @@ #include "modules/volume.hpp" -#if ENABLE_ALSA #include "adapters/alsa/control.hpp" #include "adapters/alsa/generic.hpp" #include "adapters/alsa/mixer.hpp" -#elif ENABLE_PULSEAUDIO -#include "adapters/pulseaudio.hpp" -#endif #include "drawtypes/label.hpp" #include "drawtypes/progressbar.hpp" #include "drawtypes/ramp.hpp" @@ -17,16 +13,13 @@ POLYBAR_NS -#if ENABLE_ALSA using namespace alsa; -#endif namespace modules { template class module; volume_module::volume_module(const bar_settings& bar, string name_) : event_module(bar, move(name_)) { // Load configuration values -#if ENABLE_ALSA m_mapped = m_conf.get(name(), "mapped", m_mapped); auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s); @@ -73,14 +66,6 @@ namespace modules { } catch (const control_error& err) { throw module_error(err.what()); } -#elif ENABLE_PULSEAUDIO - auto sink_name = m_conf.get(name(), "sink", ""s); - try { - m_pulseaudio = factory_util::unique(move(sink_name)); - } catch (const pulseaudio_error& err) { - throw module_error(err.what()); - } -#endif // Add formats and elements m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME}); @@ -102,18 +87,13 @@ namespace modules { } void volume_module::teardown() { -#if ENABLE_ALSA m_mixer.clear(); m_ctrl.clear(); snd_config_update_free_global(); -#elif ENABLE_PULSEAUDIO - m_pulseaudio.reset(); -#endif } bool volume_module::has_event() { // Poll for mixer and control events -#if ENABLE_ALSA try { if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) { return true; @@ -130,20 +110,12 @@ namespace modules { } catch (const alsa_exception& e) { m_log.err("%s: %s", name(), e.what()); } -#elif ENABLE_PULSEAUDIO - try { - if (m_pulseaudio->wait(25)) - return true; - } catch (const pulseaudio_error& e) { - m_log.err("%s: %s", name(), e.what()); - } -#endif + return false; } bool volume_module::update() { // Consume pending events -#if ENABLE_ALSA if (m_mixer[mixer::MASTER]) { m_mixer[mixer::MASTER]->process_events(); } @@ -156,16 +128,12 @@ namespace modules { if (m_ctrl[control::HEADPHONE]) { m_ctrl[control::HEADPHONE]->process_events(); } -#elif ENABLE_PULSEAUDIO - m_pulseaudio->process_events(); -#endif // Get volume, mute and headphone state m_volume = 100; m_muted = false; m_headphones = false; -#if ENABLE_ALSA try { if (m_mixer[mixer::MASTER]) { m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f @@ -196,16 +164,6 @@ namespace modules { } catch (const alsa_exception& err) { m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what()); } -#elif ENABLE_PULSEAUDIO - try { - if (m_pulseaudio) { - m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f; - m_muted = m_muted || m_pulseaudio->is_muted(); - } - } catch (const pulseaudio_error& err) { - m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what()); - } -#endif // Replace label tokens if (m_label_volume) { @@ -264,14 +222,11 @@ namespace modules { return false; } else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) { return false; -#if ENABLE_ALSA } else if (!m_mixer[mixer::MASTER]) { return false; -#endif } try { -#if ENABLE_ALSA vector mixers; bool headphones{m_headphones}; @@ -311,23 +266,6 @@ namespace modules { mixer->process_events(); } } -#elif ENABLE_PULSEAUDIO - if (m_pulseaudio && !m_pulseaudio->get_name().empty()) { - if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) { - m_pulseaudio->toggle_mute(); - } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) { - // cap above 100 (~150)? - m_pulseaudio->inc_volume(5); - } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) { - m_pulseaudio->inc_volume(-5); - } else { - return false; - } - if (m_pulseaudio->wait(0)) { - m_pulseaudio->process_events(); - } - } -#endif } catch (const exception& err) { m_log.err("%s: Failed to handle command (%s)", name(), err.what()); } From 14472574753e64804909249f9d0890c2c43cd4f9 Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sat, 7 Oct 2017 20:19:06 -0700 Subject: [PATCH 11/16] refactor(pulseaudio): remove wait timeout, fix indents --- include/adapters/pulseaudio.hpp | 2 +- src/adapters/pulseaudio.cpp | 16 +++++++--------- src/modules/pulseaudio.cpp | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 78c87dfe3..c7ad5f703 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -33,7 +33,7 @@ class pulseaudio { const string& get_name(); - bool wait(int timeout = -1); + bool wait(); int process_events(); int get_volume(); diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 2e613d094..7aa9b312f 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -18,7 +18,7 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { pa_threaded_mainloop_free(m_mainloop); throw pulseaudio_error("Could not create pulseaudio context."); } - + pa_context_set_state_callback(m_context, context_state_callback, this); if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) { @@ -94,11 +94,9 @@ const string& pulseaudio::get_name() { } /** - * Wait for events (timeout in ms) + * Wait for events */ -bool pulseaudio::wait(int timeout) { - // TODO wait for specified timeout - (void) timeout; +bool pulseaudio::wait() { return m_events.size() > 0; } @@ -122,11 +120,11 @@ int pulseaudio::process_events() { break; // get volume case evtype::CHANGE: - // doesn't do anything + // doesn't do anything o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(o, m_mainloop); break; - // get default sink + // get default sink case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); wait_loop(o, m_mainloop); @@ -252,7 +250,7 @@ void pulseaudio::subscribe_callback(pa_context *, pa_subscription_event_type_t t switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { case PA_SUBSCRIPTION_EVENT_NEW: This->m_events.emplace(evtype::NEW); - break; + break; case PA_SUBSCRIPTION_EVENT_CHANGE: if (idx == This->m_index) This->m_events.emplace(evtype::CHANGE); @@ -282,7 +280,7 @@ void pulseaudio::simple_callback(pa_context *, int success, void *userdata) { void pulseaudio::get_default_sink_callback(pa_context *, const pa_server_info *info, void *userdata) { pulseaudio *This = static_cast(userdata); if (info->default_sink_name) { - This->def_s_name = info->default_sink_name; + This->def_s_name = info->default_sink_name; This->s_name = info->default_sink_name; } else This->def_s_name = ""s; diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 037567b9f..a6cc86078 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -49,7 +49,7 @@ namespace modules { bool pulseaudio_module::has_event() { // Poll for mixer and control events try { - if (m_pulseaudio->wait(25)) + if (m_pulseaudio->wait()) return true; } catch (const pulseaudio_error& e) { m_log.err("%s: %s", name(), e.what()); @@ -146,7 +146,7 @@ namespace modules { } else { return false; } - if (m_pulseaudio->wait(0)) { + if (m_pulseaudio->wait()) { m_pulseaudio->process_events(); } } From 51963bb5fa3609a9857f7d3a834a76a26ca1c01e Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sat, 14 Oct 2017 11:27:43 -0700 Subject: [PATCH 12/16] refactor(pulseaudio): remove headphone ramp --- include/modules/pulseaudio.hpp | 3 --- src/modules/pulseaudio.cpp | 8 ++------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp index a2598086e..02331c637 100644 --- a/include/modules/pulseaudio.hpp +++ b/include/modules/pulseaudio.hpp @@ -31,7 +31,6 @@ namespace modules { static constexpr auto FORMAT_MUTED = "format-muted"; static constexpr auto TAG_RAMP_VOLUME = ""; - static constexpr auto TAG_RAMP_HEADPHONES = ""; static constexpr auto TAG_BAR_VOLUME = ""; static constexpr auto TAG_LABEL_VOLUME = ""; static constexpr auto TAG_LABEL_MUTED = ""; @@ -43,14 +42,12 @@ namespace modules { progressbar_t m_bar_volume; ramp_t m_ramp_volume; - ramp_t m_ramp_headphones; label_t m_label_volume; label_t m_label_muted; pulseaudio_t m_pulseaudio; atomic m_muted{false}; - atomic m_headphones{false}; atomic m_volume{0}; }; } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index a6cc86078..ab0079492 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -38,7 +38,6 @@ namespace modules { } if (m_formatter->has(TAG_RAMP_VOLUME)) { m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME); - m_ramp_headphones = load_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false); } } @@ -61,10 +60,9 @@ namespace modules { // Consume pending events m_pulseaudio->process_events(); - // Get volume, mute and headphone state + // Get volume and mute state m_volume = 100; m_muted = false; - m_headphones = false; try { if (m_pulseaudio) { @@ -113,10 +111,8 @@ namespace modules { bool pulseaudio_module::build(builder* builder, const string& tag) const { if (tag == TAG_BAR_VOLUME) { builder->node(m_bar_volume->output(m_volume)); - } else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones)) { + } else if (tag == TAG_RAMP_VOLUME) { builder->node(m_ramp_volume->get_by_percentage(m_volume)); - } else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones) { - builder->node(m_ramp_headphones->get_by_percentage(m_volume)); } else if (tag == TAG_LABEL_VOLUME) { builder->node(m_label_volume); } else if (tag == TAG_LABEL_MUTED) { From 31ccb049f9f1c72e769093d4a965a7b0c871213a Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sun, 15 Oct 2017 22:25:19 -0700 Subject: [PATCH 13/16] fix(pulseaudio): use PA_VOLUME_UI_MAX --- src/adapters/pulseaudio.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 7aa9b312f..dd098ca5d 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -172,14 +172,15 @@ void pulseaudio::set_volume(float percentage) { * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume) */ void pulseaudio::inc_volume(int delta_perc) { - // set max value? pa_threaded_mainloop_lock(m_mainloop); pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(op, m_mainloop); pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_NORM); - if (delta_perc > 0) - pa_cvolume_inc(&cv, vol); - else + if (delta_perc > 0) { + if (pa_cvolume_max(&cv) + vol <= PA_VOLUME_UI_MAX) { + pa_cvolume_inc(&cv, vol); + } + } else pa_cvolume_dec(&cv, vol); op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); From 71c8dfb2f0614a69ae3013062e66e377906359bc Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sat, 28 Oct 2017 22:50:51 -0700 Subject: [PATCH 14/16] fix(pulseaudio): Add logging --- include/adapters/pulseaudio.hpp | 6 ++++-- src/adapters/pulseaudio.cpp | 19 +++++++++++++------ src/modules/pulseaudio.cpp | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index c7ad5f703..33115f5f8 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -16,6 +16,7 @@ typedef struct pa_context pa_context; typedef struct pa_threaded_mainloop pa_threaded_mainloop; POLYBAR_NS +class logger; DEFINE_ERROR(pulseaudio_error); @@ -25,7 +26,7 @@ class pulseaudio { using queue = std::queue; public: - explicit pulseaudio(string&& sink_name); + explicit pulseaudio(const logger& logger, string&& sink_name); ~pulseaudio(); pulseaudio(const pulseaudio& o) = delete; @@ -54,11 +55,12 @@ class pulseaudio { inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop); + const logger& m_log; + // used for temporary callback results int success{0}; pa_cvolume cv; bool muted{false}; - bool exists{false}; // default sink name string def_s_name; diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index dd098ca5d..563f337bd 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -1,11 +1,12 @@ #include "adapters/pulseaudio.hpp" +#include "components/logger.hpp" POLYBAR_NS /** * Construct pulseaudio object */ -pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { +pulseaudio::pulseaudio(const logger& logger, string&& sink_name) : m_log(logger), spec_s_name(sink_name) { m_mainloop = pa_threaded_mainloop_new(); if (!m_mainloop) { throw pulseaudio_error("Could not create pulseaudio threaded mainloop."); @@ -37,6 +38,8 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { throw pulseaudio_error("Could not start pulseaudio mainloop."); } + m_log.trace("pulseaudio: started mainloop"); + pa_threaded_mainloop_wait(m_mainloop); if (pa_context_get_state(m_context) != PA_CONTEXT_READY) { pa_threaded_mainloop_unlock(m_mainloop); @@ -52,7 +55,8 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); } - if (!exists) { + if (s_name.empty()) { + printf("not found\n"); op = pa_context_get_server_info(m_context, get_default_sink_callback, this); if (!op) { throw pulseaudio_error("Failed to get pulseaudio server info."); @@ -63,6 +67,9 @@ pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) { // get the sink index op = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); + m_log.warn("pulseaudio: using default sink %s", s_name); + } else { + m_log.trace("pulseaudio: using sink %s", s_name); } op = pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_SINK, simple_callback, this); @@ -132,6 +139,7 @@ int pulseaudio::process_events() { throw pulseaudio_error("Failed to get default sink."); o = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(o, m_mainloop); + m_log.warn("pulseaudio: using default sink %s", s_name); break; } m_events.pop(); @@ -179,6 +187,8 @@ void pulseaudio::inc_volume(int delta_perc) { if (delta_perc > 0) { if (pa_cvolume_max(&cv) + vol <= PA_VOLUME_UI_MAX) { pa_cvolume_inc(&cv, vol); + } else { + m_log.warn("pulseaudio: maximum volume reached"); } } else pa_cvolume_dec(&cv, vol); @@ -293,10 +303,7 @@ void pulseaudio::get_default_sink_callback(pa_context *, const pa_server_info *i */ void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int eol, void *userdata) { pulseaudio *This = static_cast(userdata); - if (eol || !info) - This->exists = false; - else { - This->exists = true; + if (!eol && info) { This->m_index = info->index; This->s_name = info->name; } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index ab0079492..12592fda8 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -18,7 +18,7 @@ namespace modules { // Load configuration values auto sink_name = m_conf.get(name(), "sink", ""s); try { - m_pulseaudio = factory_util::unique(move(sink_name)); + m_pulseaudio = factory_util::unique(m_log, move(sink_name)); } catch (const pulseaudio_error& err) { throw module_error(err.what()); } From 1d61d672de5ba74be4bc8a18997021ceb55b41f2 Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Sat, 28 Oct 2017 23:35:26 -0700 Subject: [PATCH 15/16] fix(pulseaudio): prevent nonresponsiveness and remove redundant getters --- include/adapters/pulseaudio.hpp | 1 + src/adapters/pulseaudio.cpp | 41 +++++++++++---------------------- src/modules/pulseaudio.cpp | 3 --- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 33115f5f8..1e76df92c 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -45,6 +45,7 @@ class pulseaudio { bool is_muted(); private: + void update_volume(pa_operation *o); static void check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); static void get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int is_last, void *userdata); static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata); diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 563f337bd..62da5a4e4 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -78,6 +78,8 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name) : m_log(logger) throw pulseaudio_error("Failed to subscribe to server."); pa_context_set_subscribe_callback(m_context, subscribe_callback, this); + update_volume(op); + pa_threaded_mainloop_unlock(m_mainloop); } @@ -125,12 +127,6 @@ int pulseaudio::process_events() { wait_loop(o, m_mainloop); } break; - // get volume - case evtype::CHANGE: - // doesn't do anything - o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); - wait_loop(o, m_mainloop); - break; // get default sink case evtype::REMOVE: o = pa_context_get_server_info(m_context, get_default_sink_callback, this); @@ -141,7 +137,10 @@ int pulseaudio::process_events() { wait_loop(o, m_mainloop); m_log.warn("pulseaudio: using default sink %s", s_name); break; + default: + break; } + update_volume(o); m_events.pop(); } pa_threaded_mainloop_unlock(m_mainloop); @@ -152,10 +151,6 @@ int pulseaudio::process_events() { * Get volume in percentage */ int pulseaudio::get_volume() { - pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); - wait_loop(op, m_mainloop); - pa_threaded_mainloop_unlock(m_mainloop); // alternatively, user pa_cvolume_avg_mask() to average selected channels return static_cast(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f); } @@ -165,11 +160,9 @@ int pulseaudio::get_volume() { */ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); - wait_loop(op, m_mainloop); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); - op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); + pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); if (!success) throw pulseaudio_error("Failed to set sink volume."); @@ -181,8 +174,6 @@ void pulseaudio::set_volume(float percentage) { */ void pulseaudio::inc_volume(int delta_perc) { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); - wait_loop(op, m_mainloop); pa_volume_t vol = math_util::percentage_to_value(abs(delta_perc), PA_VOLUME_NORM); if (delta_perc > 0) { if (pa_cvolume_max(&cv) + vol <= PA_VOLUME_UI_MAX) { @@ -192,7 +183,7 @@ void pulseaudio::inc_volume(int delta_perc) { } } else pa_cvolume_dec(&cv, vol); - op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); + pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); if (!success) throw pulseaudio_error("Failed to set sink volume."); @@ -222,21 +213,15 @@ void pulseaudio::toggle_mute() { * Get current mute state */ bool pulseaudio::is_muted() { - pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_get_sink_info_by_index(m_context, m_index, check_mute_callback, this); - wait_loop(op, m_mainloop); - pa_threaded_mainloop_unlock(m_mainloop); return muted; } /** - * Callback when getting current mute state + * Update local volume cache */ -void pulseaudio::check_mute_callback(pa_context *, const pa_sink_info *info, int, void *userdata) { - pulseaudio* This = static_cast(userdata); - if (info) - This->muted = info->mute; - pa_threaded_mainloop_signal(This->m_mainloop, 0); +void pulseaudio::update_volume(pa_operation *o) { + o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); + wait_loop(o, m_mainloop); } /** @@ -244,8 +229,10 @@ void pulseaudio::check_mute_callback(pa_context *, const pa_sink_info *info, int */ void pulseaudio::get_sink_volume_callback(pa_context *, const pa_sink_info *info, int, void *userdata) { pulseaudio* This = static_cast(userdata); - if (info) + if (info) { This->cv = info->volume; + This->muted = info->mute; + } pa_threaded_mainloop_signal(This->m_mainloop, 0); } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 12592fda8..f65523cfc 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -142,9 +142,6 @@ namespace modules { } else { return false; } - if (m_pulseaudio->wait()) { - m_pulseaudio->process_events(); - } } } catch (const exception& err) { m_log.err("%s: Failed to handle command (%s)", name(), err.what()); From dc4ff45bbc80394f6a0c2756218f8265d11a8cf9 Mon Sep 17 00:00:00 2001 From: NBonaparte Date: Fri, 15 Dec 2017 22:46:46 -0800 Subject: [PATCH 16/16] fix(pulseaudio): warn only if default sink is not same as specified sink --- src/adapters/pulseaudio.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 62da5a4e4..5ec3948ba 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -56,7 +56,6 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name) : m_log(logger) wait_loop(op, m_mainloop); } if (s_name.empty()) { - printf("not found\n"); op = pa_context_get_server_info(m_context, get_default_sink_callback, this); if (!op) { throw pulseaudio_error("Failed to get pulseaudio server info."); @@ -135,7 +134,8 @@ int pulseaudio::process_events() { throw pulseaudio_error("Failed to get default sink."); o = pa_context_get_sink_info_by_name(m_context, def_s_name.c_str(), sink_info_callback, this); wait_loop(o, m_mainloop); - m_log.warn("pulseaudio: using default sink %s", s_name); + if (spec_s_name != s_name) + m_log.warn("pulseaudio: using default sink %s", s_name); break; default: break;