From 2a529357b7516922c600e5e95815da9f32dda3c9 Mon Sep 17 00:00:00 2001 From: Dave Reisner Date: Mon, 31 Dec 2012 17:05:35 -0500 Subject: initial commit --- pulse.cc | 561 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 pulse.cc (limited to 'pulse.cc') diff --git a/pulse.cc b/pulse.cc new file mode 100644 index 0000000..c09b61b --- /dev/null +++ b/pulse.cc @@ -0,0 +1,561 @@ +// Self +#include "pulse.h" + +// C +#include +#include +#include +#include + +// C++ +#include +#include + +// External +#include + +namespace { +static void connect_state_cb(pa_context* context, void* raw) { + enum pa_context_state *state = (enum pa_context_state*)raw; + *state = pa_context_get_state(context); +} + +static void success_cb(pa_context* context, int success, void* raw) { + int *r = (int*)raw; + *r = success; + if (!success) { + fprintf(stderr, + "operation failed: %s\n", + pa_strerror(pa_context_errno(context))); + } +} + +static void card_info_cb(pa_context* context, + const pa_card_info* info, + int eol, + void* raw) { + if (eol < 0) { + fprintf(stderr, "%s error in %s: \n", __func__, + pa_strerror(pa_context_errno(context))); + return; + } + + if (!eol) { + vector* cards = (vector*)raw; + cards->push_back(info); + } +} + +template +static void device_info_cb(pa_context* context, + const T* info, + int eol, + void* raw) { + if (eol < 0) { + fprintf(stderr, "%s error in %s: \n", __func__, + pa_strerror(pa_context_errno(context))); + return; + } + + if (!eol) { + vector* devices_ = (vector*)raw; + devices_->push_back(info); + } +} + +static void server_info_cb(pa_context* context __attribute__((unused)), + const pa_server_info* i, + void* raw) { + ServerInfo* defaults = (ServerInfo*)raw; + defaults->sink = i->default_sink_name; + defaults->source = i->default_source_name; +} + +static pa_cvolume* value_to_cvol(long value, pa_cvolume *cvol) { + return pa_cvolume_set(cvol, cvol->channels, + fmax((double)(value + .5) * PA_VOLUME_NORM / 100, 0)); +} + +static int volume_as_percent(const pa_cvolume* cvol) { + return pa_cvolume_avg(cvol) * 100.0 / PA_VOLUME_NORM; +} + +static int xstrtol(const char *str, long *out) { + char *end = NULL; + + if (str == NULL || *str == '\0') return -1; + errno = 0; + + *out = strtol(str, &end, 10); + if (errno || str == end || (end && *end)) return -1; + + return 0; +} + + +} // anonymous namespace + +Pulse::Pulse(string client_name) : + client_name_(client_name), + volume_range_(0, 150), + balance_range_(-100, 100) { + enum pa_context_state state = PA_CONTEXT_CONNECTING; + + pa_proplist* proplist = pa_proplist_new(); + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, client_name.c_str()); + pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.falconindy.ponymix"); + pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "0.1"); + pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card"); + + mainloop_ = pa_mainloop_new(); + context_ = pa_context_new_with_proplist(pa_mainloop_get_api(mainloop_), + nullptr, proplist); + + pa_proplist_free(proplist); + + pa_context_set_state_callback(context_, connect_state_cb, &state); + pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr); + while (state != PA_CONTEXT_READY && state != PA_CONTEXT_FAILED) { + pa_mainloop_iterate(mainloop_, 1, nullptr); + } + + if (state != PA_CONTEXT_READY) { + fprintf(stderr, "failed to connect to pulse daemon: %s\n", + pa_strerror(pa_context_errno(context_))); + exit(EXIT_FAILURE); + } +} + +// +// Pulse Client +// +Pulse::~Pulse() { + pa_context_unref(context_); + pa_mainloop_free(mainloop_); +} + +void Pulse::Populate() { + populate_server_info(); + populate_sinks(); + populate_sources(); + populate_cards(); +} + +Card* Pulse::GetCard(const uint32_t& index) { + for (Card& card : cards_) { + if (card.index_ == index) return &card; + } + return nullptr; +} + +Card* Pulse::GetCard(const string& name) { + for (Card& card : cards_) { + if (card.name_ == name) return &card; + } + return nullptr; +} + +Device* Pulse::get_device(vector& devices, const uint32_t& index) { + for (Device& device : devices) { + if (device.index_ == index) return &device; + } + return nullptr; +} + +Device* Pulse::get_device(vector& devices, const string& name) { + long val; + if (xstrtol(name.c_str(), &val) == 0) return get_device(devices, val); + + for (Device& device : devices) { + if (device.name_ == name) return &device; + } + return nullptr; +} + +Device* Pulse::GetSink(const uint32_t& index) { + return get_device(sinks_, index); +} + +Device* Pulse::GetSink(const string& name) { + return get_device(sinks_, name); +} + +Device* Pulse::GetSource(const uint32_t& index) { + return get_device(sources_, index); +} + +Device* Pulse::GetSource(const string& name) { + return get_device(sources_, name); +} + +Device* Pulse::GetSinkInput(const uint32_t& index) { + return get_device(sink_inputs_, index); +} + +Device* Pulse::GetSinkInput(const string& name) { + return get_device(sink_inputs_, name); +} + +Device* Pulse::GetSourceOutput(const uint32_t& index) { + return get_device(source_outputs_, index); +} + +Device* Pulse::GetSourceOutput(const string& name) { + return get_device(source_outputs_, name); +} + +void Pulse::mainloop_iterate(pa_operation* op) { + int r; + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_mainloop_iterate(mainloop_, 1, &r); + } +} + +void Pulse::populate_cards() { + cards_.clear(); + pa_operation* op = pa_context_get_card_info_list(context_, + card_info_cb, + (void*)&cards_); + mainloop_iterate(op); + pa_operation_unref(op); +} + +void Pulse::populate_server_info() { + pa_operation* op = pa_context_get_server_info(context_, + server_info_cb, + &defaults_); + mainloop_iterate(op); + pa_operation_unref(op); +} + +void Pulse::populate_sinks() { + sinks_.clear(); + pa_operation* op = pa_context_get_sink_info_list(context_, + device_info_cb, + (void*)&sinks_); + mainloop_iterate(op); + pa_operation_unref(op); + + sink_inputs_.clear(); + op = pa_context_get_sink_input_info_list(context_, + device_info_cb, + (void*)&sink_inputs_); + mainloop_iterate(op); + pa_operation_unref(op); +} + +void Pulse::populate_sources() { + sources_.clear(); + pa_operation* op = pa_context_get_source_info_list(context_, + device_info_cb, + (void*)&sources_); + mainloop_iterate(op); + pa_operation_unref(op); + + source_outputs_.clear(); + op = pa_context_get_source_output_info_list(context_, + device_info_cb, + (void*)&source_outputs_); + mainloop_iterate(op); + pa_operation_unref(op); +} + +bool Pulse::SetMute(Device& device, bool mute) { + int success; + + if (device.ops_.Mute == nullptr) { + warnx("device does not support muting."); + return false; + } + + pa_operation* op = device.ops_.Mute(context_, + device.index_, + mute, + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) device.mute_ = mute; + + return success; +} + +bool Pulse::SetVolume(Device& device, long volume) { + int success; + + if (device.ops_.SetVolume == nullptr) { + warnx("device does not support setting balance."); + return false; + } + + const pa_cvolume *cvol = value_to_cvol(volume, &device.volume_); + pa_operation* op = device.ops_.SetVolume(context_, + device.index_, + cvol, + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) device.update_volume(*cvol); + + return success; +} + +bool Pulse::IncreaseVolume(Device& device, long increment) { + return SetVolume(device, + volume_range_.clamp(device.volume_percent_ + increment)); +} + +bool Pulse::DecreaseVolume(Device& device, long increment) { + return SetVolume(device, + volume_range_.clamp(device.volume_percent_ - increment)); +} + +bool Pulse::SetBalance(Device& device, long balance) { + int success; + + if (device.ops_.SetVolume == nullptr) { + warnx("device does not support setting balance."); + return false; + } + + pa_cvolume *cvol = pa_cvolume_set_balance(&device.volume_, + &device.channels_, + balance / 100.0); + pa_operation* op = device.ops_.SetVolume(context_, + device.index_, + cvol, + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) device.update_volume(*cvol); + + return success; +} + +bool Pulse::IncreaseBalance(Device& device, long increment) { + return SetBalance(device, + balance_range_.clamp(device.balance_ + increment)); +} + +bool Pulse::DecreaseBalance(Device& device, long increment) { + return SetBalance(device, + balance_range_.clamp(device.balance_ - increment)); +} + +int Pulse::GetVolume(const Device& device) const { + return device.Volume(); +} + +int Pulse::GetBalance(const Device& device) const { + return device.Balance(); +} + +bool Pulse::SetProfile(Card& card, const string& profile) { + int success; + pa_operation* op = + pa_context_set_card_profile_by_index(context_, + card.index_, + profile.c_str(), + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) { + // Update the profile + for (const Profile p : card.profiles_) { + if (p.name == profile) { + card.active_profile_ = p; + break; + } + } + } + + return success; +} + +bool Pulse::Move(Device& source, Device& dest) { + int success; + + if (source.ops_.Move == nullptr) { + warnx("source device does not support moving."); + return false; + } + + pa_operation* op = source.ops_.Move(context_, + source.index_, + dest.index_, + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + return success; +} + +bool Pulse::Kill(Device& device) { + int success; + + if (device.ops_.Kill == nullptr) { + warnx("source device does not support being killed."); + return false; + } + + pa_operation* op = device.ops_.Kill(context_, + device.index_, + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) remove_device(device); + + return success; +} + +bool Pulse::SetDefault(Device& device) { + int success; + + if (device.ops_.SetDefault == nullptr) { + warnx("device does not support defaults"); + return false; + } + + pa_operation* op = device.ops_.SetDefault(context_, + device.name_.c_str(), + success_cb, + &success); + mainloop_iterate(op); + pa_operation_unref(op); + + if (success) { + switch (device.type_) { + case DEVTYPE_SINK: + defaults_.sink = device.name_; + break; + case DEVTYPE_SOURCE: + defaults_.source = device.name_; + break; + default: + errx(1, "impossible to set a default for device type %d", + device.type_); + } + } + + return success; +} + +void Pulse::remove_device(Device& device) { + vector* devlist; + + switch (device.type_) { + case DEVTYPE_SINK: + devlist = &sinks_; + case DEVTYPE_SINK_INPUT: + devlist = &sink_inputs_; + break; + case DEVTYPE_SOURCE: + devlist = &sources_; + break; + case DEVTYPE_SOURCE_OUTPUT: + devlist = &source_outputs_; + break; + } + devlist->erase( + std::remove_if( + devlist->begin(), devlist->end(), + [=](Device& d) { return d.index_ == device.index_; }), + devlist->end()); +} + +// +// Cards +// +Card::Card(const pa_card_info* info) : + index_(info->index), + name_(info->name), + owner_module_(info->owner_module), + driver_(info->driver), + active_profile_(*info->active_profile) { + for (int i = 0; info->profiles[i].name != nullptr; i++) { + profiles_.push_back(info->profiles[i]); + } +} + +// +// Devices +// +Device::Device(const pa_sink_info* info) : + type_(DEVTYPE_SINK), + index_(info->index), + name_(info->name), + desc_(info->description), + mute_(info->mute) { + update_volume(info->volume); + memcpy(&channels_, &info->channel_map, sizeof(pa_channel_map)); + balance_ = pa_cvolume_get_balance(&volume_, &channels_) * 100.0; + + ops_.SetVolume = pa_context_set_sink_volume_by_index; + ops_.Mute = pa_context_set_sink_mute_by_index; + ops_.SetDefault = pa_context_set_default_sink; +} + +Device::Device(const pa_source_info* info) : + type_(DEVTYPE_SOURCE), + index_(info->index), + name_(info->name), + desc_(info->description), + mute_(info->mute) { + update_volume(info->volume); + memcpy(&channels_, &info->channel_map, sizeof(pa_channel_map)); + balance_ = pa_cvolume_get_balance(&volume_, &channels_) * 100.0; + + ops_.SetVolume = pa_context_set_source_volume_by_index; + ops_.Mute = pa_context_set_source_mute_by_index; + ops_.SetDefault = pa_context_set_default_source; +} + +Device::Device(const pa_sink_input_info* info) : + type_(DEVTYPE_SINK_INPUT), + index_(info->index), + name_(info->name), + desc_(pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME)), + mute_(info->mute) { + update_volume(info->volume); + memcpy(&channels_, &info->channel_map, sizeof(pa_channel_map)); + balance_ = pa_cvolume_get_balance(&volume_, &channels_) * 100.0; + + ops_.SetVolume = pa_context_set_sink_input_volume; + ops_.Mute = pa_context_set_sink_input_mute; + ops_.Kill = pa_context_kill_sink_input; + ops_.Move = pa_context_move_sink_input_by_index; +} + +Device::Device(const pa_source_output_info* info) : + type_(DEVTYPE_SOURCE_OUTPUT), + index_(info->index), + name_(info->name), + desc_(pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME)), + mute_(info->mute) { + update_volume(info->volume); + volume_percent_ = volume_as_percent(&volume_); + balance_ = pa_cvolume_get_balance(&volume_, &channels_) * 100.0; + + ops_.SetVolume = pa_context_set_source_output_volume; + ops_.Mute = pa_context_set_source_output_mute; + ops_.Kill = pa_context_kill_source_output; + ops_.Move = pa_context_move_source_output_by_index; +} + +void Device::update_volume(const pa_cvolume& newvol) { + memcpy(&volume_, &newvol, sizeof(pa_cvolume)); + volume_percent_ = volume_as_percent(&volume_); + balance_ = pa_cvolume_get_balance(&volume_, &channels_) * 100.0; +} + +// vim: set et ts=2 sw=2: -- cgit v1.2.3