aboutsummaryrefslogtreecommitdiffstats
path: root/ponymix.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ponymix.cc')
-rw-r--r--ponymix.cc642
1 files changed, 642 insertions, 0 deletions
diff --git a/ponymix.cc b/ponymix.cc
new file mode 100644
index 0000000..fc20315
--- /dev/null
+++ b/ponymix.cc
@@ -0,0 +1,642 @@
+#include "pulse.h"
+
+#include <err.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <stdexcept>
+
+struct Command {
+ int (*fn)(PulseClient&, int, char*[]);
+ Range<int> args;
+};
+
+struct Color {
+ Color() {
+ if (isatty(fileno(stdout))) {
+ name = "\033[1m";
+ reset = "\033[0m";
+ over9000 = "\033[7;31m";
+ veryhigh = "\033[31m";
+ high = "\033[35m";
+ mid = "\033[33m";
+ low = "\033[32m";
+ verylow = "\033[34m";
+ mute = "\033[1;31m";
+ } else {
+ name = "";
+ reset = "";
+ over9000 = "";
+ veryhigh = "";
+ high = "";
+ mid = "";
+ low = "";
+ verylow = "";
+ mute = "";
+ }
+ }
+
+ const char* name;
+ const char* reset;
+
+ // Volume levels
+ const char* over9000;
+ const char* veryhigh;
+ const char* high;
+ const char* mid;
+ const char* low;
+ const char* verylow;
+ const char* mute;
+};
+
+static enum DeviceType opt_devtype;
+static bool opt_listrestrict;
+static const char* opt_action;
+static const char* opt_device;
+static const char* opt_card;
+static Color color;
+
+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! */
+ throw std::out_of_range("device type out of range");
+}
+
+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_or_die(PulseClient& ponymix,
+ string arg,
+ enum DeviceType type) {
+ Device* device = ponymix.GetDevice(arg, type);
+ if (device == nullptr) errx(1, "no match found for device: %s", arg.c_str());
+ return device;
+}
+
+static void Print(const Device& device, bool shirt) {
+ if (shirt) {
+ printf("%s\t%d\t%s\t%s\n",
+ type_to_string(device.Type()),
+ device.Index(),
+ device.Name().c_str(),
+ device.Desc().c_str());
+ return;
+ }
+
+ const char *mute = device.Muted() ? " [Muted]" : "";
+ const char *volume_color;
+
+ if (device.Volume() < 20) {
+ volume_color = color.verylow;
+ } else if (device.Volume() < 40) {
+ volume_color = color.low;
+ } else if (device.Volume() < 60) {
+ volume_color = color.mid;
+ } else if (device.Volume() < 80) {
+ volume_color = color.high;
+ } else if (device.Volume() <= 100) {
+ volume_color = color.veryhigh;
+ } else {
+ volume_color = color.over9000;
+ }
+
+ printf("%s%s %d:%s %s\n"
+ " %s\n"
+ " Avg. Volume: %s%d%%%s%s%s%s\n",
+ color.name,
+ type_to_string(device.Type()),
+ device.Index(),
+ color.reset,
+ device.Name().c_str(),
+ device.Desc().c_str(),
+ volume_color,
+ device.Volume(),
+ color.reset,
+ color.mute,
+ mute,
+ color.reset);
+}
+
+static void Print(const Card& card, bool shirt) {
+ if (shirt) {
+ printf("%s\n", card.Name().c_str());
+ return;
+ }
+
+ printf("%scard %d:%s %s\n"
+ " Driver: %s\n"
+ " Active Profile: %s\n",
+ color.name,
+ card.Index(),
+ color.reset,
+ card.Name().c_str(),
+ card.Driver().c_str(),
+ card.ActiveProfile().name.c_str());
+}
+
+static void Print(const Profile& profile, bool active, bool shirt) {
+ if (shirt) {
+ printf("%s\n", profile.name.c_str());
+ return;
+ }
+
+ const char* active_str = active ? " [active]" : "";
+ printf("%s%s%s%s%s%s\n"
+ " %s\n",
+ color.name,
+ profile.name.c_str(),
+ color.reset,
+ color.low,
+ active_str,
+ color.reset,
+ profile.desc.c_str());
+}
+
+static int ShowDefaults(PulseClient& ponymix, int, char*[]) {
+ const auto& info = ponymix.GetDefaults();
+ Print(*ponymix.GetSink(info.sink), false);
+ Print(*ponymix.GetSource(info.source), false);
+ return 0;
+}
+
+static int list_devices(PulseClient& ponymix, bool shirt) {
+ if (opt_listrestrict) {
+ const auto& devices = ponymix.GetDevices(opt_devtype);
+ for (const auto& d : devices) Print(d, shirt);
+ return 0;
+ }
+
+ const auto& sinks = ponymix.GetSinks();
+ for (const auto& s : sinks) Print(s, shirt);
+
+ const auto& sources = ponymix.GetSources();
+ for (const auto& s : sources) Print(s, shirt);
+
+ const auto& sinkinputs = ponymix.GetSinkInputs();
+ for (const auto& s : sinkinputs) Print(s, shirt);
+
+ const auto& sourceoutputs = ponymix.GetSourceOutputs();
+ for (const auto& s : sourceoutputs) Print(s, shirt);
+
+ return 0;
+}
+
+static int List(PulseClient& ponymix, int, char*[]) {
+ return list_devices(ponymix, false);
+}
+
+static int ListShort(PulseClient& ponymix, int, char*[]) {
+ return list_devices(ponymix, true);
+}
+
+static int list_cards(PulseClient& ponymix, bool shirt) {
+ const auto& cards = ponymix.GetCards();
+ for (const auto& c : cards) Print(c, shirt);
+
+ return 0;
+}
+
+static int ListCards(PulseClient& ponymix, int, char*[]) {
+ return list_cards(ponymix, false);
+}
+
+static int ListCardsShort(PulseClient& ponymix, int, char*[]) {
+ return list_cards(ponymix, true);
+}
+
+static Card* resolve_active_card_or_die(PulseClient& ponymix) {
+ Card* card;
+ if (opt_card == nullptr) {
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ card = ponymix.GetCard(*device);
+ if (card == nullptr) errx(1, "error: no card found or selected.");
+ } else {
+ card = ponymix.GetCard(opt_card);
+ if (card == nullptr) {
+ errx(1, "error: no match found for card: %s", opt_card);
+ }
+ }
+
+ return card;
+}
+
+static int list_profiles(PulseClient& ponymix, bool shirt) {
+ auto card = resolve_active_card_or_die(ponymix);
+
+ const auto& profiles = card->Profiles();
+ for (const auto& p : profiles) Print(p,
+ p.name == card->ActiveProfile().name,
+ shirt);
+
+ return 0;
+}
+
+static int ListProfiles(PulseClient& ponymix, int, char*[]) {
+ return list_profiles(ponymix, false);
+}
+
+static int ListProfilesShort(PulseClient& ponymix, int, char*[]) {
+ return list_profiles(ponymix, true);
+}
+
+static int GetVolume(PulseClient& ponymix, int, char*[]) {
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ printf("%d\n", device->Volume());
+ return 0;
+}
+
+static int SetVolume(PulseClient& ponymix, int, char* argv[]) {
+ 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(PulseClient& ponymix, int, char*[]) {
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ printf("%d\n", device->Balance());
+ return 0;
+}
+
+static int SetBalance(PulseClient& ponymix, int, char* argv[]) {
+ 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(PulseClient& ponymix, int, char* argv[]) {
+ 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(PulseClient& ponymix, int, char* argv[]) {
+ 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(PulseClient& ponymix, int, char* argv[]) {
+ 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(PulseClient& ponymix, int, char*[]) {
+ 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(PulseClient& ponymix, int, char*[]) {
+ 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(PulseClient& ponymix, int, char*[]) {
+ 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(PulseClient& ponymix, int, char*[]) {
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+ return !ponymix.IsMuted(*device);
+}
+
+static int SetDefault(PulseClient& ponymix, int, char*[]) {
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ return !ponymix.SetDefault(*device);
+}
+
+static int GetProfile(PulseClient& ponymix, int, char*[]) {
+ auto card = resolve_active_card_or_die(ponymix);
+ printf("%s\n", card->ActiveProfile().name.c_str());
+
+ return true;
+}
+
+static int SetProfile(PulseClient& ponymix, int, char* argv[]) {
+ auto card = resolve_active_card_or_die(ponymix);
+ return !ponymix.SetProfile(*card, argv[0]);
+}
+
+static int Move(PulseClient& ponymix, int, char* argv[]) {
+ // this assignment is a lie. stfu g++
+ enum DeviceType target_devtype = opt_devtype;
+ switch (opt_devtype) {
+ case DEVTYPE_SOURCE:
+ opt_devtype = DEVTYPE_SOURCE_OUTPUT;
+ case DEVTYPE_SOURCE_OUTPUT:
+ target_devtype = DEVTYPE_SOURCE;
+ break;
+ case DEVTYPE_SINK:
+ opt_devtype = DEVTYPE_SINK_INPUT;
+ case DEVTYPE_SINK_INPUT:
+ target_devtype = DEVTYPE_SINK;
+ break;
+ }
+
+ // 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], target_devtype);
+
+ return !ponymix.Move(*source, *target);
+}
+
+static int Kill(PulseClient& ponymix, int, char*[]) {
+ switch (opt_devtype) {
+ case DEVTYPE_SOURCE:
+ opt_devtype = DEVTYPE_SOURCE_OUTPUT;
+ break;
+ case DEVTYPE_SINK:
+ opt_devtype = DEVTYPE_SINK_INPUT;
+ break;
+ default:
+ break;
+ }
+
+ auto device = string_to_device_or_die(ponymix, opt_device, opt_devtype);
+
+ return !ponymix.Kill(*device);
+}
+
+static const Command& string_to_command(const char* str) {
+ static std::map<string, const Command> actionmap = {
+ // command name function arg min arg max
+ { "defaults", { ShowDefaults, { 0, 0 } } },
+ { "list", { List, { 0, 0 } } },
+ { "list-short", { ListShort, { 0, 0 } } },
+ { "list-cards", { ListCards, { 0, 0 } } },
+ { "list-cards-short", { ListCardsShort, { 0, 0 } } },
+ { "list-profiles", { ListProfiles, { 0, 0 } } },
+ { "list-profiles-short", { ListProfilesShort, { 0, 0 } } },
+ { "get-volume", { GetVolume, { 0, 0 } } },
+ { "set-volume", { SetVolume, { 1, 1 } } },
+ { "get-balance", { GetBalance, { 0, 0 } } },
+ { "set-balance", { SetBalance, { 1, 1 } } },
+ { "adj-balance", { AdjBalance, { 1, 1 } } },
+ { "increase", { IncreaseVolume, { 1, 1 } } },
+ { "decrease", { DecreaseVolume, { 1, 1 } } },
+ { "mute", { Mute, { 0, 0 } } },
+ { "unmute", { Unmute, { 0, 0 } } },
+ { "toggle", { ToggleMute, { 0, 0 } } },
+ { "is-muted", { IsMuted, { 0, 0 } } },
+ { "set-default", { SetDefault, { 0, 0 } } },
+ { "get-profile", { GetProfile, { 0, 0 } } },
+ { "set-profile", { SetProfile, { 1, 1 } } },
+ { "move", { Move, { 1, 1 } } },
+ { "kill", { Kill, { 0, 0 } } }
+ };
+
+ try {
+ return actionmap.at(str);
+ } catch(std::out_of_range) {
+ errx(1, "error: Invalid action specified: %s", str);
+ }
+}
+
+static 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"
+ " --source alias to -t source\n"
+ " --input alais to -t source\n"
+ " --sink alias to -t sink\n"
+ " --output alias to -t sink\n"
+ " --sink-input alias to -t sink-input\n"
+ " --source-output alias to -t source-output\n", stdout);
+
+ fputs("\nDevice Commands:\n"
+ " help display this message\n"
+ " defaults list default devices (default command)\n"
+ " set-default set default device by ID\n"
+ " list list available devices\n"
+ " list-short list available devices (short form)\n"
+ " list-cards list available cards\n"
+ " list-cards-short list available cards (short form)\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("\nApplication Commands:\n"
+ " move DEVICE move target device to DEVICE\n"
+ " kill DEVICE kill target DEVICE\n", stdout);
+
+ fputs("\nCard Commands:\n"
+ " list-profiles list available profiles for a card\n"
+ " list-profiles-short list available profiles for a card (short form)\n"
+ " get-profile get active profile for card\n"
+ " set-profile PROFILE set profile for a card\n", stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+void error_wrong_args(const Command& cmd, const char* cmdname) {
+ if (cmd.args.min == cmd.args.max) {
+ errx(1, "error: %s takes exactly %d argument%c",
+ cmdname, cmd.args.min, cmd.args.min == 1 ? '\0' : 's');
+ } else {
+ errx(1, "error: %s takes %d to %d arguments\n",
+ cmdname, cmd.args.min, cmd.args.max);
+ }
+}
+
+static int CommandDispatch(PulseClient& ponymix, int argc, char *argv[]) {
+ if (argc > 0) {
+ opt_action = argv[0];
+ argv++;
+ argc--;
+ }
+
+ if (strcmp(opt_action, "help") == 0) {
+ usage();
+ return 0;
+ }
+
+ const Command& cmd = string_to_command(opt_action);
+ if (cmd.args.InRange(argc) != 0) error_wrong_args(cmd, opt_action);
+
+ return cmd.fn(ponymix, argc, argv);
+}
+
+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' },
+ { "sink", no_argument, 0, 0x100 },
+ { "output", no_argument, 0, 0x101 },
+ { "source", no_argument, 0, 0x102 },
+ { "input", no_argument, 0, 0x103 },
+ { "sink-input", no_argument, 0, 0x104 },
+ { "source-output", no_argument, 0, 0x105 },
+ { 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;
+ case 0x100:
+ case 0x101:
+ opt_devtype = DEVTYPE_SINK;
+ opt_listrestrict = true;
+ break;
+ case 0x102:
+ case 0x103:
+ opt_devtype = DEVTYPE_SOURCE;
+ opt_listrestrict = true;
+ break;
+ case 0x104:
+ opt_devtype = DEVTYPE_SINK_INPUT;
+ opt_listrestrict = true;
+ break;
+ case 0x105:
+ opt_devtype = DEVTYPE_SOURCE_OUTPUT;
+ opt_listrestrict = true;
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int main(int argc, char* argv[]) {
+ PulseClient ponymix("ponymix");
+ ponymix.Populate();
+
+ // defaults. intentionally, we don't set a card -- only get
+ // that on demand if a function needs it.
+ ServerInfo defaults = ponymix.GetDefaults();
+ opt_action = "defaults";
+ opt_devtype = DEVTYPE_SINK;
+ opt_device = defaults.sink.c_str();
+
+ if (!parse_options(argc, argv)) return 1;
+ argc -= optind;
+ argv += optind;
+
+ return CommandDispatch(ponymix, argc, argv);
+}
+
+// vim: set et ts=2 sw=2: