aboutsummaryrefslogtreecommitdiffstats
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
downloadmirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.tar.gz
mirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.tar.bz2
mirror-ponymix-2a529357b7516922c600e5e95815da9f32dda3c9.zip
initial commit
-rw-r--r--Makefile22
-rw-r--r--ponymix.cc520
-rw-r--r--pulse.cc561
-rw-r--r--pulse.h216
4 files changed, 1319 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e6cb6ca
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+CXX = g++ -std=c++11
+
+base_CFLAGS = -Wall -Wextra -pedantic -O2 -g
+base_LIBS = -lm
+
+libpulse_CFLAGS = $(shell pkg-config --cflags libpulse)
+libpulse_LIBS = $(shell pkg-config --libs libpulse)
+
+CXXFLAGS := $(base_CFLAGS) $(libpulse_CFLAGS) $(CXXFLAGS)
+LDLIBS := $(base_LIBS) $(libpulse_LIBS)
+
+all: ponymix
+
+ponymix: ponymix.cc pulse.o
+pulse.o: pulse.cc pulse.h
+
+install: ponymix
+ install -Dm755 ponymix $(DESTDIR)/usr/bin/ponymix
+ install -Dm644 ponymix.1 $(DESTDIR)/usr/share/man/man1/ponymix.1
+
+clean:
+ $(RM) ponymix pulse.o
diff --git a/ponymix.cc b/ponymix.cc
new file mode 100644
index 0000000..1bd256f
--- /dev/null
+++ b/ponymix.cc
@@ -0,0 +1,520 @@
+#include "pulse.h"
+
+#include <err.h>
+#include <getopt.h>
+
+#include <algorithm>
+#include <map>
+#include <stdexcept>
+
+#define _unused_ __attribute__((unused))
+
+enum Action {
+ ACTION_DEFAULTS,
+ ACTION_LIST,
+ ACTION_LISTCARDS,
+ ACTION_LISTPROFILES,
+ ACTION_GETVOL,
+ ACTION_SETVOL,
+ ACTION_GETBAL,
+ ACTION_SETBAL,
+ ACTION_ADJBAL,
+ ACTION_INCREASE,
+ ACTION_DECREASE,
+ ACTION_MUTE,
+ ACTION_UNMUTE,
+ ACTION_TOGGLE,
+ ACTION_ISMUTED,
+ ACTION_SETDEFAULT,
+ ACTION_GETPROFILE,
+ ACTION_SETPROFILE,
+ ACTION_MOVE,
+ ACTION_KILL,
+ ACTION_INVALID,
+};
+
+static enum DeviceType opt_devtype;
+static enum Action opt_action;
+static const char* opt_device;
+static const char* opt_card;
+
+static const int kMinVolume = 0;
+static const int kMaxVolume = 150;
+static const int kMinBalance = -100;
+static const int kMaxBalance = 100;
+
+static const char* type_to_string(enum DeviceType t) {
+ switch (t) {
+ case DEVTYPE_SINK:
+ return "sink";
+ case DEVTYPE_SOURCE:
+ return "source";
+ case DEVTYPE_SINK_INPUT:
+ return "sink input";
+ case DEVTYPE_SOURCE_OUTPUT:
+ return "source output";
+ }
+
+ /* impossibiru! */
+ return NULL;
+}
+
+static enum Action string_to_action(const char* str) {
+ static std::map<string, enum Action> actionmap = {
+ { "defaults", ACTION_DEFAULTS },
+ { "list", ACTION_LIST },
+ { "list-cards", ACTION_LISTCARDS },
+ { "list-profiles", ACTION_LISTPROFILES },
+ { "get-volume", ACTION_GETVOL },
+ { "set-volume", ACTION_SETVOL },
+ { "get-balance", ACTION_GETBAL },
+ { "set-balance", ACTION_SETBAL },
+ { "adj-balance", ACTION_ADJBAL },
+ { "increase", ACTION_INCREASE },
+ { "decrease", ACTION_DECREASE },
+ { "mute", ACTION_MUTE },
+ { "unmute", ACTION_UNMUTE },
+ { "toggle", ACTION_TOGGLE },
+ { "is-muted", ACTION_ISMUTED },
+ { "set-default", ACTION_SETDEFAULT },
+ { "get-profile", ACTION_GETPROFILE },
+ { "set-profile", ACTION_SETPROFILE },
+ { "move", ACTION_MOVE },
+ { "kill", ACTION_KILL }
+ };
+
+ try {
+ return actionmap.at(str);
+ } catch(std::out_of_range) {
+ errx(1, "error: Invalid option specified: %s", str);
+ }
+}
+
+static enum DeviceType string_to_devtype_or_die(const char* str) {
+ static std::map<string, enum DeviceType> typemap = {
+ { "sink", DEVTYPE_SINK },
+ { "source", DEVTYPE_SOURCE },
+ { "sink-input", DEVTYPE_SINK_INPUT },
+ { "source-output", DEVTYPE_SOURCE_OUTPUT },
+ };
+ try {
+ return typemap.at(str);
+ } catch(std::out_of_range) {
+ errx(1, "error: Invalid device type specified: %s", str);
+ }
+}
+
+static Device* string_to_device(Pulse& ponymix, string arg, enum DeviceType type) {
+ switch (type) {
+ case DEVTYPE_SINK:
+ return ponymix.GetSink(arg);
+ case DEVTYPE_SOURCE:
+ return ponymix.GetSource(arg);
+ case DEVTYPE_SOURCE_OUTPUT:
+ return ponymix.GetSourceOutput(arg);
+ case DEVTYPE_SINK_INPUT:
+ return ponymix.GetSinkInput(arg);
+ default:
+ return nullptr;
+ }
+}
+
+static Device* string_to_device_or_die(Pulse& ponymix,
+ string arg,
+ enum DeviceType type) {
+ Device* device = string_to_device(ponymix, arg, type);
+ if (device == nullptr) errx(1, "no match found for device: %s", arg.c_str());
+ return device;
+}
+
+static void Print(const Device& device) {
+ printf("%s %d: %s\n"
+ " %s\n"
+ " Avg. Volume: %d%%\n",
+ type_to_string(device.Type()),
+ device.Index(),
+ device.Name().c_str(),
+ device.Desc().c_str(),
+ device.Volume());
+}
+
+static void Print(const Card& card) {
+ printf("%s\n", card.Name().c_str());
+}
+
+static void Print(const Profile& profile, bool active) {
+ printf("%s: %s%s\n",
+ profile.name.c_str(), profile.desc.c_str(), active ? " [active]" : "");
+}
+
+static int ShowDefaults(Pulse& ponymix,
+ int argc _unused_,
+ char* argv[] _unused_) {
+ const auto& info = ponymix.GetDefaults();
+ Print(*ponymix.GetSink(info.sink));
+ Print(*ponymix.GetSource(info.source));
+ return 0;
+}
+
+static int List(Pulse& ponymix, int argc _unused_, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: list requires 0 arguments");
+
+ const auto& sinks = ponymix.GetSinks();
+ for (const auto& s : sinks) Print(s);
+
+ const auto& sources = ponymix.GetSources();
+ for (const auto& s : sources) Print(s);
+
+ const auto& sinkinputs = ponymix.GetSinkInputs();
+ for (const auto& s : sinkinputs) Print(s);
+
+ const auto& sourceoutputs = ponymix.GetSourceOutputs();
+ for (const auto& s : sourceoutputs) Print(s);
+
+ return 0;
+}
+
+static int ListCards(Pulse& ponymix, int argc _unused_, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: list-cards requires 0 arguments");
+
+ const auto& cards = ponymix.GetCards();
+ for (const auto& c : cards) Print(c);
+
+ return 0;
+}
+
+static int ListProfiles(Pulse& ponymix, int argc _unused_, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: list-profiles requires 0 arguments");
+
+ // TODO: figure out how to get a list of cards?
+ auto card = ponymix.GetCard(opt_card);
+ if (card == nullptr) errx(1, "error: no match found for card: %s", opt_card);
+
+ const auto& profiles = card->Profiles();
+ for (const auto& p : profiles) Print(p, p.name == card->ActiveProfile().name);
+
+ return 0;
+}
+
+static int GetVolume(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: get-volume requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ printf("%d\n", device->Volume());
+ return 0;
+}
+
+static int SetVolume(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: set-volume requires exactly 1 argument");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ long volume;
+ try {
+ volume = std::stol(argv[0]);
+ } catch (std::invalid_argument _) {
+ errx(1, "error: failed to convert string to integer: %s", argv[0]);
+ }
+
+ if (!ponymix.SetVolume(*device, volume)) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int GetBalance(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: get-balance requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ printf("%d\n", device->Balance());
+ return 0;
+}
+
+static int SetBalance(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: set-balance requires exactly 1 argument");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ long balance;
+ try {
+ balance = std::stol(argv[0]);
+ } catch (std::invalid_argument _) {
+ errx(1, "error: failed to convert string to integer: %s", argv[0]);
+ }
+
+ if (!ponymix.SetBalance(*device, balance)) return 1;
+
+ printf("%d\n", device->Balance());
+
+ return 0;
+}
+
+static int AdjBalance(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: adj-balance requires exactly 1 argument");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ long balance;
+ try {
+ balance = std::stol(argv[0]);
+ } catch (std::invalid_argument _) {
+ errx(1, "error: failed to convert string to integer: %s", argv[0]);
+ }
+
+ if (!ponymix.SetBalance(*device, device->Balance() + balance)) return 1;
+
+ printf("%d\n", device->Balance());
+
+ return 0;
+}
+
+static int IncreaseVolume(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: increase requires exactly 1 argument");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ long delta;
+ try {
+ delta = std::stol(argv[0]);
+ } catch (std::invalid_argument _) {
+ errx(1, "error: failed to convert string to integer: %s", argv[0]);
+ }
+
+ if (!ponymix.SetVolume(*device, device->Volume() + delta)) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int DecreaseVolume(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: decrease requires exactly 1 argument");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ long delta;
+ try {
+ delta = std::stol(argv[0]);
+ } catch (std::invalid_argument _) {
+ errx(1, "error: failed to convert string to integer: %s", argv[0]);
+ }
+
+ if (!ponymix.SetVolume(*device, device->Volume() - delta)) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int Mute(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: mute requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ if (!ponymix.SetMute(*device, true)) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int Unmute(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: unmute requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ if (!ponymix.SetMute(*device, false)) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int ToggleMute(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: toggle requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ if (!ponymix.SetMute(*device, !ponymix.IsMuted(*device))) return 1;
+
+ printf("%d\n", device->Volume());
+
+ return 0;
+}
+
+static int IsMuted(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: is-muted requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ return !ponymix.IsMuted(*device);
+}
+
+static int SetDefault(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: set-default requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ return !ponymix.SetDefault(*device);
+}
+
+static int GetProfile(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: get-profile requires 0 arguments");
+
+ auto card = ponymix.GetCard(opt_card);
+ if (card == nullptr) errx(1, "error: no match found for card: %s", opt_card);
+
+ printf("%s\n", card->ActiveProfile().name.c_str());
+
+ return true;
+}
+
+static int SetProfile(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 1) errx(1, "error: set-profile requires 1 argument");
+
+ auto card = ponymix.GetCard(opt_card);
+ if (card == nullptr) errx(1, "error: no match found for card: %s", opt_card);
+
+ return !ponymix.SetProfile(*card, argv[0]);
+}
+
+static int Move(Pulse& ponymix, int argc, char* argv[]) {
+ if (argc != 1) errx(1, "error: move requires 1 argument");
+
+ // Does this even work?
+ auto source = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ auto target = string_to_device_or_die(ponymix, argv[0], opt_devtype);
+
+ return !ponymix.Move(*source, *target);
+}
+
+static int Kill(Pulse& ponymix, int argc, char* argv[] _unused_) {
+ if (argc != 0) errx(1, "error: set-default requires 0 arguments");
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ return !ponymix.Kill(*device);
+}
+
+static int (*fn[])(Pulse& ponymix, int argc, char* argv[]) = {
+ [ACTION_DEFAULTS] = ShowDefaults,
+ [ACTION_LIST] = List,
+ [ACTION_LISTCARDS] = ListCards,
+ [ACTION_LISTPROFILES] = ListProfiles,
+ [ACTION_GETVOL] = GetVolume,
+ [ACTION_SETVOL] = SetVolume,
+ [ACTION_GETBAL] = GetBalance,
+ [ACTION_SETBAL] = SetBalance,
+ [ACTION_ADJBAL] = AdjBalance,
+ [ACTION_INCREASE] = IncreaseVolume,
+ [ACTION_DECREASE] = DecreaseVolume,
+ [ACTION_MUTE] = Mute,
+ [ACTION_UNMUTE] = Unmute,
+ [ACTION_TOGGLE] = ToggleMute,
+ [ACTION_ISMUTED] = IsMuted,
+ [ACTION_SETDEFAULT] = SetDefault,
+ [ACTION_GETPROFILE] = GetProfile,
+ [ACTION_SETPROFILE] = SetProfile,
+ [ACTION_MOVE] = Move,
+ [ACTION_KILL] = Kill,
+};
+
+void usage() {
+ printf("usage: %s [options] <command>...\n", program_invocation_short_name);
+ fputs("\nOptions:\n"
+ " -h, --help display this help and exit\n\n"
+
+ " -c, --card CARD target card (index or name)\n"
+ " -d, --device DEVICE target device (index or name)\n"
+ " -t, --devtype TYPE device type\n", stdout);
+
+ fputs("\nCommon Commands:\n"
+ " list list available devices\n"
+ " list-cards list available cards\n"
+ " get-volume get volume for device\n"
+ " set-volume VALUE set volume for device\n"
+ " get-balance get balance for device\n"
+ " set-balance VALUE set balance for device\n"
+ " adj-balance VALUE increase or decrease balance for device\n"
+ " increase VALUE increase volume\n", stdout);
+ fputs(" decrease VALUE decrease volume\n"
+ " mute mute device\n"
+ " unmute unmute device\n"
+ " toggle toggle mute\n"
+ " is-muted check if muted\n", stdout);
+
+ fputs("\nCard Commands:\n"
+ " list-profiles list available profiles for a card\n"
+ " get-profile get active profile for card\n"
+ " set-profile PROFILE set profile for a card\n"
+
+ "\nDevice Commands:\n"
+ " defaults list default devices (default command)\n"
+ " set-default DEVICE set default device by ID\n"
+
+ "\nApplication Commands:\n"
+ " move DEVICE move target device to DEVICE\n"
+ " kill DEVICE kill target DEVICE\n", stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+bool parse_options(int argc, char** argv) {
+ static const struct option opts[] = {
+ { "card", required_argument, 0, 'c' },
+ { "device", required_argument, 0, 'd' },
+ { "help", no_argument, 0, 'h' },
+ { "type", required_argument, 0, 't' },
+ { 0, 0, 0, 0 },
+ };
+
+ for (;;) {
+ int opt = getopt_long(argc, argv, "c:d:ht:", opts, NULL);
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 'c':
+ opt_card = optarg;
+ break;
+ case 'd':
+ opt_device = optarg;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 't':
+ opt_devtype = string_to_devtype_or_die(optarg);
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int main(int argc, char* argv[]) {
+ Pulse ponymix("ponymix");
+ ponymix.Populate();
+
+ // defaults
+ opt_action = ACTION_DEFAULTS;
+ opt_devtype = DEVTYPE_SINK;
+ opt_device = ponymix.GetDefaults().sink.c_str();
+ opt_card = ponymix.GetCards()[0].Name().c_str();
+
+ if (!parse_options(argc, argv)) return 1;
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ opt_action = string_to_action(argv[0]);
+ argc--;
+ argv++;
+ }
+
+ return fn[opt_action](ponymix, argc, argv);
+}
+
+// vim: set et ts=2 sw=2:
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:
diff --git a/pulse.h b/pulse.h
new file mode 100644
index 0000000..1e91352
--- /dev/null
+++ b/pulse.h
@@ -0,0 +1,216 @@
+#pragma once
+
+// C
+#include <string.h>
+
+// C++
+#include <memory>
+#include <string>
+#include <vector>
+
+// external
+#include <pulse/pulseaudio.h>
+
+using std::string;
+using std::vector;
+using std::unique_ptr;
+
+enum DeviceType {
+ DEVTYPE_SINK,
+ DEVTYPE_SOURCE,
+ DEVTYPE_SINK_INPUT,
+ DEVTYPE_SOURCE_OUTPUT,
+};
+
+struct Profile {
+ Profile(const pa_card_profile_info& info) :
+ name(info.name),
+ desc(info.description) {}
+
+ string name;
+ string desc;
+};
+
+struct Operations {
+ pa_operation* (*Mute)(pa_context*, uint32_t, int, pa_context_success_cb_t, void*);
+ pa_operation* (*SetVolume)(pa_context*, uint32_t, const pa_cvolume*, pa_context_success_cb_t, void*);
+ pa_operation* (*SetDefault)(pa_context*, const char*, pa_context_success_cb_t, void*);
+ pa_operation* (*Kill)(pa_context*, uint32_t, pa_context_success_cb_t, void*);
+ pa_operation* (*Move)(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t, void *);
+};
+
+class Device {
+ public:
+ Device(const pa_source_info* info);
+ Device(const pa_sink_info* info);
+ Device(const pa_sink_input_info* info);
+ Device(const pa_source_output_info* info);
+
+ uint32_t Index() const { return index_; }
+ const string& Name() const { return name_; }
+ const string& Desc() const { return desc_; }
+ int Volume() const { return volume_percent_; }
+ int Balance() const { return balance_; }
+ bool Muted() const { return mute_; }
+ enum DeviceType Type() const { return type_; }
+
+ private:
+ friend class Pulse;
+
+ void update_volume(const pa_cvolume& newvol);
+
+ enum DeviceType type_;
+ uint32_t index_;
+ string name_;
+ string desc_;
+ pa_cvolume volume_;
+ int volume_percent_;
+ pa_channel_map channels_;
+ int mute_;
+ int balance_;
+ Operations ops_;
+};
+
+class Card {
+ public:
+ Card(const pa_card_info* info);
+
+ const string& Name() const { return name_; }
+ uint32_t Index() const { return index_; }
+ const string& Driver() const { return driver_; }
+
+ const vector<Profile>& Profiles() const { return profiles_; }
+ const Profile& ActiveProfile() const { return active_profile_; }
+
+ private:
+ friend class Pulse;
+
+ uint32_t index_;
+ string name_;
+ uint32_t owner_module_;
+ string driver_;
+ vector<Profile> profiles_;
+ Profile active_profile_;
+};
+
+struct ServerInfo {
+ string sink;
+ string source;
+};
+
+template<typename T>
+struct Range {
+ Range(T min, T max) : min(min), max(max) {}
+
+ T clamp(T value) {
+ return value < min ? min : (value > max ? max : value);
+ }
+
+ T min;
+ T max;
+};
+
+class Pulse {
+ public:
+ Pulse(string client_name);
+ ~Pulse();
+
+ // Populates all known devices and cards. Any currently known
+ // devices and cards are cleared before the new data is stored.
+ void Populate();
+
+ // Get a sink by index or name, or all sinks.
+ Device* GetSink(const uint32_t& index);
+ Device* GetSink(const string& name);
+ const vector<Device>& GetSinks() const { return sinks_; }
+
+ // Get a source by index or name, or all sources.
+ Device* GetSource(const uint32_t& index);
+ Device* GetSource(const string& name);
+ const vector<Device>& GetSources() const { return sources_; }
+
+ // Get a sink input by index or name, or all sink inputs.
+ Device* GetSinkInput(const uint32_t& name);
+ Device* GetSinkInput(const string& name);
+ const vector<Device>& GetSinkInputs() const { return sink_inputs_; }
+
+ // Get a source output by index or name, or all source outputs.
+ Device* GetSourceOutput(const uint32_t& name);
+ Device* GetSourceOutput(const string& name);
+ const vector<Device>& GetSourceOutputs() const { return source_outputs_; }
+
+ // Get a card by index or name, or all cards.
+ Card* GetCard(const uint32_t& index);
+ Card* GetCard(const string& name);
+ const vector<Card>& GetCards() const { return cards_; }
+
+ // Get or set the volume of a device.
+ int GetVolume(const Device& device) const;
+ bool SetVolume(Device& device, long value);
+
+ // Convenience wrappers for adjusting volume
+ bool IncreaseVolume(Device& device, long increment);
+ bool DecreaseVolume(Device& device, long decrement);
+
+ // Get or set the volume of a device. Not all devices support this.
+ int GetBalance(const Device& device) const;
+ bool SetBalance(Device& device, long value);
+
+ // Convenience wrappers for adjusting balance
+ bool IncreaseBalance(Device& device, long increment);
+ bool DecreaseBalance(Device& device, long decrement);
+
+ // Get and set mute for a device.
+ bool IsMuted(const Device& device) const { return device.mute_; };
+ bool SetMute(Device& device, bool mute);
+
+ // Set the profile for a card by name.
+ bool SetProfile(Card& card, const string& profile);
+
+ // Move a given source output or sink input to the destination.
+ bool Move(Device& source, Device& dest);
+
+ // Kill a source output or sink input.
+ bool Kill(Device& device);
+
+ // Get or set the default sink and source.
+ const ServerInfo& GetDefaults() const { return defaults_; }
+ bool SetDefault(Device& device);
+
+ // Set minimum and maximum allowed volume
+ void SetVolumeRange(int min, int max) {
+ volume_range_ = { min, max };
+ }
+
+ // Set minimum and maximum allowed balance
+ void SetBalanceRange(int min, int max) {
+ balance_range_ = { min, max };
+ }
+
+ private:
+ void mainloop_iterate(pa_operation* op);
+
+ void populate_server_info();
+ void populate_cards();
+ void populate_sinks();
+ void populate_sources();
+
+ Device* get_device(vector<Device>& devices, const uint32_t& index);
+ Device* get_device(vector<Device>& devices, const string& name);
+
+ void remove_device(Device& device);
+
+ string client_name_;
+ pa_context* context_;
+ pa_mainloop* mainloop_;
+ vector<Device> sinks_;
+ vector<Device> sources_;
+ vector<Device> sink_inputs_;
+ vector<Device> source_outputs_;
+ vector<Card> cards_;
+ ServerInfo defaults_;
+ Range<int> volume_range_;
+ Range<int> balance_range_;
+};
+
+// vim: set et ts=2 sw=2: