This commit is contained in:
Vincent Bernat 2024-01-30 16:24:55 +08:00 committed by GitHub
commit efc11eba54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 165 additions and 65 deletions

View File

@ -27,7 +27,8 @@ class pulseaudio {
using queue = std::queue<evtype>; using queue = std::queue<evtype>;
public: public:
explicit pulseaudio(const logger& logger, string&& sink_name, bool m_max_volume); enum class devicetype { SINK = 0, SOURCE };
explicit pulseaudio(const logger& logger, devicetype device_type, string&& device_name, bool m_max_volume);
~pulseaudio(); ~pulseaudio();
pulseaudio(const pulseaudio& o) = delete; pulseaudio(const pulseaudio& o) = delete;
@ -40,23 +41,29 @@ class pulseaudio {
int get_volume(); int get_volume();
double get_decibels(); double get_decibels();
void set_volume(float percentage);
void inc_volume(int delta_perc); void inc_volume(int delta_perc);
void set_mute(bool mode); void set_mute(bool mode);
void toggle_mute(); void toggle_mute();
bool is_muted(); bool is_muted();
private: private:
void update_volume(pa_operation* o); template<typename T>
static void check_mute_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata); static void get_volume_callback(pa_context* context, const T* info, int is_last, 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 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 simple_callback(pa_context* context, int success, void* userdata);
static void sink_info_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata); template<typename T>
static void info_callback(pa_context* context, const T* info, int eol, void* userdata);
static void context_state_callback(pa_context* context, void* userdata); static void context_state_callback(pa_context* context, void* userdata);
inline void wait_loop(pa_operation* op, pa_threaded_mainloop* loop); inline void wait_loop(pa_operation* op, pa_threaded_mainloop* loop);
/* Abstraction for sink/source */
const string get_device_type();
void device_update_info(string device_name);
void device_update_info();
void device_set_volume();
void device_set_mute(bool mode);
const logger& m_log; const logger& m_log;
/** /**
@ -70,13 +77,15 @@ class pulseaudio {
bool muted{false}; bool muted{false};
// default sink name // default sink name
static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@"; static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@";
static constexpr auto DEFAULT_SOURCE = "@DEFAULT_SOURCE@";
pa_context* m_context{nullptr}; pa_context* m_context{nullptr};
pa_threaded_mainloop* m_mainloop{nullptr}; pa_threaded_mainloop* m_mainloop{nullptr};
queue m_events; queue m_events;
// specified sink name // specified sink/source
devicetype m_device_type;
string spec_s_name; string spec_s_name;
string s_name; string s_name;
uint32_t m_index{0}; uint32_t m_index{0};

View File

@ -7,8 +7,9 @@ POLYBAR_NS
/** /**
* Construct pulseaudio object * Construct pulseaudio object
*/ */
pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume) pulseaudio::pulseaudio(const logger& logger, devicetype device_type, string&& device_name, bool max_volume)
: m_log(logger), spec_s_name(sink_name) { : m_log(logger), spec_s_name(device_name) {
m_device_type = device_type;
m_mainloop = pa_threaded_mainloop_new(); m_mainloop = pa_threaded_mainloop_new();
if (!m_mainloop) { if (!m_mainloop) {
throw pulseaudio_error("Could not create pulseaudio threaded mainloop."); throw pulseaudio_error("Could not create pulseaudio threaded mainloop.");
@ -65,30 +66,38 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume
} }
pa_operation* op{nullptr}; pa_operation* op{nullptr};
if (!sink_name.empty()) { if (!device_name.empty()) {
op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); device_update_info(device_name);
wait_loop(op, m_mainloop);
} }
if (s_name.empty()) { if (s_name.empty()) {
// get the sink index // get the index
op = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this); device_update_info(""s);
wait_loop(op, m_mainloop); m_log.notice("pulseaudio: using default %s %s", get_device_type(), s_name);
m_log.notice("pulseaudio: using default sink %s", s_name);
} else { } else {
m_log.trace("pulseaudio: using sink %s", s_name); m_log.trace("pulseaudio: using %s %s", get_device_type(), s_name);
} }
m_max_volume = max_volume ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM; m_max_volume = max_volume ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM;
auto event_types = static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER); pa_subscription_mask_t event_types;
switch (m_device_type) {
case devicetype::SINK:
event_types = static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER);
break;
case devicetype::SOURCE:
event_types = static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER);
break;
default:
throw pulseaudio_error("unreachable");
}
op = pa_context_subscribe(m_context, event_types, simple_callback, this); op = pa_context_subscribe(m_context, event_types, simple_callback, this);
wait_loop(op, m_mainloop); wait_loop(op, m_mainloop);
if (!success) { if (!success) {
throw pulseaudio_error("Failed to subscribe to sink."); throw pulseaudio_error("Failed to subscribe to device.");
} }
pa_context_set_subscribe_callback(m_context, subscribe_callback, this); pa_context_set_subscribe_callback(m_context, subscribe_callback, this);
update_volume(op); device_update_info();
pa_threaded_mainloop_unlock(m_mainloop); pa_threaded_mainloop_unlock(m_mainloop);
} }
@ -110,6 +119,87 @@ const string& pulseaudio::get_name() {
return s_name; return s_name;
} }
/**
* Get device type as string
*/
const string pulseaudio::get_device_type() {
switch (m_device_type) {
case devicetype::SINK:
return "sink"s;
case devicetype::SOURCE:
return "source"s;
}
throw pulseaudio_error("unreachable");
}
/**
* Wrapper around pa_context_get_XXX_info_by_name().
*/
void pulseaudio::device_update_info(string device_name) {
pa_operation *op{nullptr};
switch (m_device_type) {
case devicetype::SINK:
op = pa_context_get_sink_info_by_name(m_context,
device_name.empty()?DEFAULT_SINK:device_name.c_str(),
info_callback<pa_sink_info>, this);
break;
case devicetype::SOURCE:
op = pa_context_get_source_info_by_name(m_context,
device_name.empty()?DEFAULT_SOURCE:device_name.c_str(),
info_callback<pa_source_info>, this);
break;
}
wait_loop(op, m_mainloop);
}
/**
* Wrapper around pa_context_get_XXX_info_by_index().
*/
void pulseaudio::device_update_info() {
pa_operation *op{nullptr};
switch (m_device_type) {
case devicetype::SINK:
op = pa_context_get_sink_info_by_index(m_context, m_index, get_volume_callback<pa_sink_info>, this);
break;
case devicetype::SOURCE:
op = pa_context_get_source_info_by_index(m_context, m_index, get_volume_callback<pa_source_info>, this);
break;
}
wait_loop(op, m_mainloop);
}
/**
* Wrapper around pa_context_set_XXX_volume_by_index().
*/
void pulseaudio::device_set_volume() {
pa_operation *op{nullptr};
switch (m_device_type) {
case devicetype::SINK:
op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this);
break;
case devicetype::SOURCE:
op = pa_context_set_source_volume_by_index(m_context, m_index, &cv, simple_callback, this);
break;
}
wait_loop(op, m_mainloop);
}
/**
* Wrapper around pa_context_set_XXX_mute_by_index().
*/
void pulseaudio::device_set_mute(bool mode) {
pa_operation *op{nullptr};
switch (m_device_type) {
case devicetype::SINK:
op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this);
break;
case devicetype::SOURCE:
op = pa_context_set_source_mute_by_index(m_context, m_index, mode, simple_callback, this);
break;
}
wait_loop(op, m_mainloop);
}
/** /**
* Wait for events * Wait for events
*/ */
@ -123,37 +213,34 @@ bool pulseaudio::wait() {
int pulseaudio::process_events() { int pulseaudio::process_events() {
int ret = m_events.size(); int ret = m_events.size();
pa_threaded_mainloop_lock(m_mainloop); pa_threaded_mainloop_lock(m_mainloop);
pa_operation* o{nullptr};
// clear the queue // clear the queue
while (!m_events.empty()) { while (!m_events.empty()) {
switch (m_events.front()) { switch (m_events.front()) {
// try to get specified sink // try to get specified sink/source
case evtype::NEW: case evtype::NEW:
// redundant if already using specified sink // redundant if already using specified sink/source
if (!spec_s_name.empty()) { if (!spec_s_name.empty()) {
o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this); device_update_info(spec_s_name);
wait_loop(o, m_mainloop);
break; break;
} }
// FALLTHRU // FALLTHRU
case evtype::SERVER: case evtype::SERVER:
// don't fallthrough only if always using default sink // don't fallthrough only if always using default sink/source
if (!spec_s_name.empty()) { if (!spec_s_name.empty()) {
break; break;
} }
// FALLTHRU // FALLTHRU
// get default sink // get default sink/source
case evtype::REMOVE: case evtype::REMOVE:
o = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this); device_update_info(""s);
wait_loop(o, m_mainloop);
if (spec_s_name != s_name) { if (spec_s_name != s_name) {
m_log.notice("pulseaudio: using default sink %s", s_name); m_log.notice("pulseaudio: using default %s %s", get_device_type(), s_name);
} }
break; break;
default: default:
break; break;
} }
update_volume(o); device_update_info();
m_events.pop(); m_events.pop();
} }
pa_threaded_mainloop_unlock(m_mainloop); pa_threaded_mainloop_unlock(m_mainloop);
@ -175,20 +262,6 @@ double pulseaudio::get_decibels() {
return pa_sw_volume_to_dB(pa_cvolume_max(&cv)); return pa_sw_volume_to_dB(pa_cvolume_max(&cv));
} }
/**
* Set volume to given percentage
*/
void pulseaudio::set_volume(float percentage) {
pa_threaded_mainloop_lock(m_mainloop);
pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM);
pa_cvolume_scale(&cv, vol);
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.");
pa_threaded_mainloop_unlock(m_mainloop);
}
/** /**
* Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume) * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume)
*/ */
@ -207,10 +280,9 @@ void pulseaudio::inc_volume(int delta_perc) {
} }
} else } else
pa_cvolume_dec(&cv, vol); pa_cvolume_dec(&cv, vol);
pa_operation* op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); device_set_volume();
wait_loop(op, m_mainloop);
if (!success) if (!success)
throw pulseaudio_error("Failed to set sink volume."); throw pulseaudio_error("Failed to set device volume.");
pa_threaded_mainloop_unlock(m_mainloop); pa_threaded_mainloop_unlock(m_mainloop);
} }
@ -219,10 +291,9 @@ void pulseaudio::inc_volume(int delta_perc) {
*/ */
void pulseaudio::set_mute(bool mode) { void pulseaudio::set_mute(bool mode) {
pa_threaded_mainloop_lock(m_mainloop); pa_threaded_mainloop_lock(m_mainloop);
pa_operation* op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this); device_set_mute(mode);
wait_loop(op, m_mainloop);
if (!success) if (!success)
throw pulseaudio_error("Failed to mute sink."); throw pulseaudio_error("Failed to mute device.");
pa_threaded_mainloop_unlock(m_mainloop); pa_threaded_mainloop_unlock(m_mainloop);
} }
@ -240,18 +311,11 @@ bool pulseaudio::is_muted() {
return muted; return muted;
} }
/**
* Update local volume cache
*/
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);
}
/** /**
* Callback when getting volume * Callback when getting volume
*/ */
void pulseaudio::get_sink_volume_callback(pa_context*, const pa_sink_info* info, int, void* userdata) { template <typename T>
void pulseaudio::get_volume_callback(pa_context*, const T* info, int, void* userdata) {
pulseaudio* This = static_cast<pulseaudio*>(userdata); pulseaudio* This = static_cast<pulseaudio*>(userdata);
if (info) { if (info) {
This->cv = info->volume; This->cv = info->volume;
@ -274,6 +338,7 @@ void pulseaudio::subscribe_callback(pa_context*, pa_subscription_event_type_t t,
} }
break; break;
case PA_SUBSCRIPTION_EVENT_SINK: case PA_SUBSCRIPTION_EVENT_SINK:
case PA_SUBSCRIPTION_EVENT_SOURCE:
switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
case PA_SUBSCRIPTION_EVENT_NEW: case PA_SUBSCRIPTION_EVENT_NEW:
This->m_events.emplace(evtype::NEW); This->m_events.emplace(evtype::NEW);
@ -302,10 +367,11 @@ void pulseaudio::simple_callback(pa_context*, int success, void* userdata) {
} }
/** /**
* Callback when getting sink info & existence * Callback when getting sink/source info & existence
*/ */
void pulseaudio::sink_info_callback(pa_context*, const pa_sink_info* info, int eol, void* userdata) { template <typename T>
pulseaudio* This = static_cast<pulseaudio*>(userdata); void pulseaudio::info_callback(pa_context*, const T* info, int eol, void* userdata) {
pulseaudio *This = static_cast<pulseaudio*>(userdata);
if (!eol && info) { if (!eol && info) {
This->m_index = info->index; This->m_index = info->index;
This->s_name = info->name; This->s_name = info->name;

View File

@ -26,11 +26,25 @@ namespace modules {
m_unmute_on_scroll = m_conf.get(name(), "unmute-on-scroll", m_unmute_on_scroll); m_unmute_on_scroll = m_conf.get(name(), "unmute-on-scroll", m_unmute_on_scroll);
auto sink_name = m_conf.get(name(), "sink", ""s); auto sink_name = m_conf.get(name(), "sink", ""s);
auto source_name = m_conf.get(name(), "source", ""s);
string device_name;
bool m_max_volume = m_conf.get(name(), "use-ui-max", true); bool m_max_volume = m_conf.get(name(), "use-ui-max", true);
m_reverse_scroll = m_conf.get(name(), "reverse-scroll", false); m_reverse_scroll = m_conf.get(name(), "reverse-scroll", false);
if (!sink_name.empty() && !source_name.empty()) {
throw module_error("Use either source or sink, not both");
}
pulseaudio::devicetype device_type;
if (!source_name.empty()) {
device_type = pulseaudio::devicetype::SOURCE;
device_name = move(source_name);
} else {
device_type = pulseaudio::devicetype::SINK;
device_name = move(sink_name);
}
try { try {
m_pulseaudio = std::make_unique<pulseaudio>(m_log, move(sink_name), m_max_volume); m_pulseaudio = std::make_unique<pulseaudio>(m_log, device_type, move(device_name), m_max_volume);
} catch (const pulseaudio_error& err) { } catch (const pulseaudio_error& err) {
throw module_error(err.what()); throw module_error(err.what());
} }
@ -85,7 +99,7 @@ namespace modules {
m_muted = m_muted || m_pulseaudio->is_muted(); m_muted = m_muted || m_pulseaudio->is_muted();
} }
} catch (const pulseaudio_error& err) { } catch (const pulseaudio_error& err) {
m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what()); m_log.err("%s: Failed to query pulseaudio (%s)", name(), err.what());
} }
// Replace label tokens // Replace label tokens
@ -109,6 +123,17 @@ namespace modules {
} }
string pulseaudio_module::get_output() { string pulseaudio_module::get_output() {
// Exclude monitors and auto_null
if (!m_pulseaudio) {
return ""s;
}
auto s_name = m_pulseaudio->get_name();
if (s_name.empty() ||
s_name == "auto_null" ||
(s_name.size() >= 8 && s_name.compare(s_name.size() - 8, s_name.size(), ".monitor") == 0)) {
return ""s;
}
// Get the module output early so that // Get the module output early so that
// the format prefix/suffix also gets wrapper // the format prefix/suffix also gets wrapper
// with the cmd handlers // with the cmd handlers