aboutsummaryrefslogtreecommitdiffstats
path: root/pulse.cc
diff options
context:
space:
mode:
authorDave Reisner <dreisner@archlinux.org>2012-12-31 17:05:35 -0500
committerDave Reisner <dreisner@archlinux.org>2013-01-01 20:58:58 -0500
commit2a529357b7516922c600e5e95815da9f32dda3c9 (patch)
treea594529fc207d6bb8bb3042184618853cdc15316 /pulse.cc
downloadmirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.tar.gz
mirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.tar.bz2
mirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.zip
initial commit
Diffstat (limited to 'pulse.cc')
-rw-r--r--pulse.cc561
1 files changed, 561 insertions, 0 deletions
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 <err.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// C++
+#include <string>
+#include <algorithm>
+
+// External
+#include <pulse/pulseaudio.h>
+
+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<Card>* cards = (vector<Card>*)raw;
+ cards->push_back(info);
+ }
+}
+
+template<typename T>
+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<Device>* devices_ = (vector<Device>*)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<Device>& devices, const uint32_t& index) {
+ for (Device& device : devices) {
+ if (device.index_ == index) return &device;
+ }
+ return nullptr;
+}
+
+Device* Pulse::get_device(vector<Device>& 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<Device>* 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: