diff
Makefile | 12 +
README.md | 39 +++
mixer.c | 794 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 845 insertions(+)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..21e95c9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+CC ?= cc
+CFLAGS ?= -O2 -Wall -Wextra -Werror -std=c89
+ALSA_CFLAGS = $(shell pkg-config --cflags alsa)
+ALSA_LIBS = $(shell pkg-config --libs alsa)
+
+all: mixer
+
+mixer: mixer.c
+ $(CC) $(CFLAGS) $(ALSA_CFLAGS) -o $@ mixer.c $(ALSA_LIBS)
+
+clean:
+ rm -f mixer
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..76d8353
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+mixer
+
+Jank (ASS) Alsa Mixer.
+
+dependencies:
+ alsa
+
+build:
+ make
+
+make (un)install:
+ make install
+ make uninstall
+
+usage:
+ mixer -t
+ mixer -l
+ mixer -c Master -g
+ mixer -c Master -s 80
+ mixer -c Master -m 1
+
+arguments:
+ -t TUI [default if no arguments passed.]
+ -l list playback controls
+ -d card sound card [default: "default" ]
+ -c name control name [required for -g/-s/-m ]
+ -g get volume [percent ]
+ -s pct set volume [0-100 ]
+ -m 0|1 mute [off/on ]
+
+keybinds:
+ j/k or arrows move
+ +/- volume
+ [/] switch ALSA card
+ m mute
+ M mute all
+ U unmute all
+ ? help
+ q quit
diff --git a/mixer.c b/mixer.c
new file mode 100644
index 0000000..cdff6bd
--- /dev/null
+++ b/mixer.c
@@ -0,0 +1,794 @@
+#include <sys/types.h>
+
+#include <alsa/asoundlib.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#define MAX_ELEMS 1024
+#define MAX_CARDNAME 64
+#define NAME_W 24
+
+struct elem_list {
+ snd_mixer_elem_t **elems;
+ int n;
+};
+
+struct card_list {
+ int *ids;
+ char **names;
+ int n;
+ int cur;
+};
+
+static const char *control;
+static const char *progname;
+static int do_get;
+static int do_list;
+static int do_mute = -1;
+static int do_set = -1;
+static int do_tui;
+
+static struct termios saved_termios;
+static volatile sig_atomic_t sig_exit;
+
+static void die(const char *);
+static int elem_can_playback(snd_mixer_elem_t *);
+static int get_elem_name(snd_mixer_elem_t *, char *, size_t);
+static int get_elem_mute(snd_mixer_elem_t *, int *);
+static int get_elem_volume(snd_mixer_elem_t *, long *);
+static const char *getprogname(void);
+static int list_elems(snd_mixer_t *);
+static int load_cards(struct card_list *);
+static int load_elems(snd_mixer_t *, struct elem_list *);
+static int open_mixer(snd_mixer_t **, const char *);
+static int set_elem_mute(snd_mixer_elem_t *, int);
+static int set_elem_volume(snd_mixer_elem_t *, long);
+static long volume_step(snd_mixer_elem_t *);
+static int set_all_mute(snd_mixer_t *, int);
+static void show_help(void);
+static int update_card(struct card_list *, char *, size_t);
+static void usage(void);
+static size_t xstrlcpy(char *, const char *, size_t);
+
+static const char *
+getprogname(void)
+{
+ if (progname == NULL || *progname == '\0')
+ return "mixer";
+ return progname;
+}
+
+static size_t
+xstrlcpy(char *dst, const char *src, size_t size)
+{
+ size_t i;
+
+ if (size == 0)
+ return strlen(src);
+ for (i = 0; i + 1 < size && src[i] != '\0'; i++)
+ dst[i] = src[i];
+ dst[i] = '\0';
+ while (src[i] != '\0')
+ i++;
+ return i;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-t] [-l] [-d card] [-c control] "
+ "[-g] [-m 0|1] [-s percent]\n", getprogname());
+ exit(1);
+}
+
+static void
+signal_handler(int sig)
+{
+ (void)sig;
+ sig_exit = 1;
+}
+
+static void
+set_signal_handlers(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = signal_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ warn("sigaction SIGINT");
+ if (sigaction(SIGTERM, &sa, NULL) == -1)
+ warn("sigaction SIGTERM");
+}
+
+static void
+check_termios(void)
+{
+ if (tcgetattr(STDIN_FILENO, &saved_termios) == -1)
+ die("tcgetattr");
+}
+
+static void
+restore_termios(void)
+{
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_termios) == -1)
+ warn("tcsetattr");
+}
+
+static void
+raw_termios(void)
+{
+ struct termios raw;
+
+ raw = saved_termios;
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
+ raw.c_cc[VMIN] = 1;
+ raw.c_cc[VTIME] = 0;
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
+ die("tcsetattr");
+}
+
+static void
+cleanup(void)
+{
+ restore_termios();
+}
+
+static void
+setup_tui(void)
+{
+ check_termios();
+ raw_termios();
+ atexit(cleanup);
+}
+
+static void
+clear_screen(void)
+{
+ printf("\033[2J\033[H");
+}
+
+static void
+hide_cursor(void)
+{
+ printf("\033[?25l");
+}
+
+static void
+show_cursor(void)
+{
+ printf("\033[?25h");
+}
+
+static void
+flush_screen(void)
+{
+ fflush(stdout);
+}
+
+static void
+print_header(const char *card)
+{
+ printf("mixer [%.20s] j/k:move +/-:vol\n", card);
+ printf("[/]:card m:mute ?:help q:quit\n\n");
+}
+
+static void
+print_elem_line(int idx, int cur, const char *name, long vol, int mute)
+{
+ if (idx == cur)
+ printf("> ");
+ else
+ printf(" ");
+
+ if (mute)
+ printf("%-*.*s %3ld%% muted\n", NAME_W, NAME_W, name, vol);
+ else
+ printf("%-*.*s %3ld%%\n", NAME_W, NAME_W, name, vol);
+}
+
+static int
+read_key(void)
+{
+ unsigned char c;
+
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ return -1;
+ if (c != '\033')
+ return c;
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ return '\033';
+ if (c != '[')
+ return '\033';
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ return '\033';
+
+ switch (c) {
+ case 'A':
+ return 'k';
+ case 'B':
+ return 'j';
+ case 'C':
+ return '+';
+ case 'D':
+ return '-';
+ default:
+ return '\033';
+ }
+}
+
+static void
+show_help(void)
+{
+ clear_screen();
+ printf("mixer help\n\n");
+ printf("j/k or arrows:\tmove\n");
+ printf("+/-:\t\tvolume\n");
+ printf("[/]:\t\tcard\n");
+ printf("m:\t\tmute\n");
+ printf("M:\t\tmute all\n");
+ printf("U:\t\tunmute all\n");
+ printf("q:\t\tquit\n");
+ printf("\npress any key to return\n");
+ flush_screen();
+ (void)read_key();
+}
+
+static void
+free_elems(struct elem_list *list)
+{
+ free(list->elems);
+ list->elems = NULL;
+ list->n = 0;
+}
+
+static void
+update_volume(snd_mixer_elem_t *elem, int delta)
+{
+ long step;
+ long max;
+ long min;
+ long raw;
+
+ if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
+ return;
+ if (max <= min)
+ return;
+ if (snd_mixer_selem_get_playback_volume(elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &raw) < 0)
+ return;
+ step = volume_step(elem);
+ raw += delta * step;
+ if (raw < min)
+ raw = min;
+ if (raw > max)
+ raw = max;
+ if (snd_mixer_selem_set_playback_volume_all(elem, raw) < 0)
+ return;
+}
+
+static void
+render_loop(snd_mixer_t **mixer, struct card_list *cards)
+{
+ snd_mixer_t *curm;
+ struct elem_list list;
+ char card[MAX_CARDNAME];
+ char name[128];
+ int cur;
+ int key;
+ int mute;
+ long vol;
+
+ memset(&list, 0, sizeof(list));
+ cur = 0;
+ update_card(cards, card, sizeof(card));
+ if (open_mixer(mixer, card) == -1)
+ die("open mixer");
+ curm = *mixer;
+ if (load_elems(curm, &list) == -1)
+ die("load_elems");
+
+ while (!sig_exit) {
+ clear_screen();
+ print_header(card);
+ if (list.n == 0)
+ printf("(no playback controls)\n");
+ for (key = 0; key < list.n; key++) {
+ if (get_elem_name(list.elems[key], name,
+ sizeof(name)) == -1)
+ xstrlcpy(name, "?", sizeof(name));
+ if (get_elem_volume(list.elems[key], &vol) == -1)
+ vol = 0;
+ if (get_elem_mute(list.elems[key], &mute) == -1)
+ mute = 0;
+ print_elem_line(key, cur, name, vol, mute);
+ }
+ flush_screen();
+
+ key = read_key();
+ switch (key) {
+ case 'q':
+ case 'Q':
+ free_elems(&list);
+ return;
+ case 'j':
+ if (cur + 1 < list.n)
+ cur++;
+ break;
+ case 'k':
+ if (cur > 0)
+ cur--;
+ break;
+ case '+':
+ case '=':
+ if (list.n > 0)
+ update_volume(list.elems[cur], 1);
+ break;
+ case '-':
+ if (list.n > 0)
+ update_volume(list.elems[cur], -1);
+ break;
+ case 'm':
+ if (list.n > 0) {
+ if (get_elem_mute(list.elems[cur], &mute) == 0)
+ (void)set_elem_mute(list.elems[cur], !mute);
+ }
+ break;
+ case 'M':
+ (void)set_all_mute(curm, 1);
+ break;
+ case 'U':
+ (void)set_all_mute(curm, 0);
+ break;
+ case '[':
+ if (cards->n > 1) {
+ cards->cur--;
+ if (cards->cur < 0)
+ cards->cur = cards->n - 1;
+ free_elems(&list);
+ snd_mixer_close(curm);
+ update_card(cards, card, sizeof(card));
+ if (open_mixer(&curm, card) == -1)
+ die("open mixer");
+ *mixer = curm;
+ if (load_elems(curm, &list) == -1)
+ die("load_elems");
+ if (cur >= list.n)
+ cur = 0;
+ }
+ break;
+ case ']':
+ if (cards->n > 1) {
+ cards->cur++;
+ if (cards->cur >= cards->n)
+ cards->cur = 0;
+ free_elems(&list);
+ snd_mixer_close(curm);
+ update_card(cards, card, sizeof(card));
+ if (open_mixer(&curm, card) == -1)
+ die("open mixer");
+ *mixer = curm;
+ if (load_elems(curm, &list) == -1)
+ die("load_elems");
+ if (cur >= list.n)
+ cur = 0;
+ }
+ break;
+ case '?':
+ show_help();
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+run_tui(snd_mixer_t **mixer)
+{
+ struct card_list cards;
+
+ memset(&cards, 0, sizeof(cards));
+ if (load_cards(&cards) == -1)
+ die("load cards");
+ setup_tui();
+ hide_cursor();
+ render_loop(mixer, &cards);
+ show_cursor();
+ free(cards.ids);
+ if (cards.names != NULL) {
+ int i;
+ for (i = 0; i < cards.n; i++)
+ free(cards.names[i]);
+ free(cards.names);
+ }
+}
+
+static int
+elem_can_playback(snd_mixer_elem_t *elem)
+{
+ if (snd_mixer_selem_is_active(elem) == 0)
+ return 0;
+ if (snd_mixer_selem_has_playback_volume(elem) == 0)
+ return 0;
+ return 1;
+}
+
+static int
+get_elem_name(snd_mixer_elem_t *elem, char *buf, size_t len)
+{
+ const char *name;
+
+ name = snd_mixer_selem_get_name(elem);
+ if (name == NULL)
+ return -1;
+ if (xstrlcpy(buf, name, len) >= len)
+ buf[len - 1] = '\0';
+ return 0;
+}
+
+static int
+get_elem_volume(snd_mixer_elem_t *elem, long *out)
+{
+ long max, min, v;
+
+ if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
+ return -1;
+ if (max <= min)
+ return -1;
+ if (snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT,
+ &v) < 0)
+ return -1;
+ v = (v - min) * 100 / (max - min);
+ if (v < 0)
+ v = 0;
+ if (v > 100)
+ v = 100;
+ *out = v;
+ return 0;
+}
+
+static long
+volume_step(snd_mixer_elem_t *elem)
+{
+ long max, min, v;
+
+ if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
+ return 1;
+ if (max <= min)
+ return 1;
+ v = (max - min) / 50;
+ if (v < 1)
+ v = 1;
+ return v;
+}
+
+static int
+set_elem_volume(snd_mixer_elem_t *elem, long percent)
+{
+ long max, min, v;
+
+ if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0)
+ return -1;
+ if (percent < 0)
+ percent = 0;
+ if (percent > 100)
+ percent = 100;
+ v = min + (percent * (max - min) / 100);
+ if (snd_mixer_selem_set_playback_volume_all(elem, v) < 0)
+ return -1;
+ return 0;
+}
+
+static int
+get_elem_mute(snd_mixer_elem_t *elem, int *out)
+{
+ int sw;
+
+ if (snd_mixer_selem_has_playback_switch(elem) == 0) {
+ *out = 0;
+ return 0;
+ }
+ if (snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT,
+ &sw) < 0)
+ return -1;
+ *out = (sw == 0);
+ return 0;
+}
+
+static int
+set_elem_mute(snd_mixer_elem_t *elem, int mute)
+{
+ int sw;
+
+ if (snd_mixer_selem_has_playback_switch(elem) == 0)
+ return 0;
+ sw = mute ? 0 : 1;
+ if (snd_mixer_selem_set_playback_switch_all(elem, sw) < 0)
+ return -1;
+ return 0;
+}
+
+static int
+set_all_mute(snd_mixer_t *mixer, int mute)
+{
+ snd_mixer_elem_t *elem;
+
+ for (elem = snd_mixer_first_elem(mixer); elem != NULL;
+ elem = snd_mixer_elem_next(elem)) {
+ if (!elem_can_playback(elem))
+ continue;
+ (void)set_elem_mute(elem, mute);
+ }
+ return 0;
+}
+
+static int
+load_elems(snd_mixer_t *mixer, struct elem_list *list)
+{
+ snd_mixer_elem_t *elem;
+
+ list->elems = calloc(MAX_ELEMS, sizeof(list->elems[0]));
+ if (list->elems == NULL)
+ return -1;
+ list->n = 0;
+
+ for (elem = snd_mixer_first_elem(mixer); elem != NULL;
+ elem = snd_mixer_elem_next(elem)) {
+ if (!elem_can_playback(elem))
+ continue;
+ if (list->n >= MAX_ELEMS)
+ break;
+ list->elems[list->n++] = elem;
+ }
+ return 0;
+}
+
+static int
+list_elems(snd_mixer_t *mixer)
+{
+ snd_mixer_elem_t *elem;
+ char name[128];
+ int mute;
+ long l;
+
+ for (elem = snd_mixer_first_elem(mixer); elem != NULL;
+ elem = snd_mixer_elem_next(elem)) {
+ if (!elem_can_playback(elem))
+ continue;
+ if (get_elem_name(elem, name, sizeof(name)) == -1)
+ xstrlcpy(name, "?", sizeof(name));
+ if (get_elem_volume(elem, &l) == -1)
+ l = 0;
+ if (get_elem_mute(elem, &mute) == -1)
+ mute = 0;
+ printf("%s\t%ld\t%s\n", name, l, mute ? "mute" : "play");
+ }
+ return 0;
+}
+
+static snd_mixer_elem_t *
+find_elem(snd_mixer_t *mixer, const char *name)
+{
+ snd_mixer_elem_t *elem;
+ char buf[128];
+
+ for (elem = snd_mixer_first_elem(mixer); elem != NULL;
+ elem = snd_mixer_elem_next(elem)) {
+ if (!elem_can_playback(elem))
+ continue;
+ if (get_elem_name(elem, buf, sizeof(buf)) == -1)
+ continue;
+ if (strcmp(buf, name) == 0)
+ return elem;
+ }
+ return NULL;
+}
+
+static int
+open_mixer(snd_mixer_t **out, const char *card)
+{
+ snd_mixer_t *mixer;
+ int r;
+
+ r = snd_mixer_open(&mixer, 0);
+ if (r < 0)
+ return -1;
+ r = snd_mixer_attach(mixer, card);
+ if (r < 0)
+ goto fail;
+ r = snd_mixer_selem_register(mixer, NULL, NULL);
+ if (r < 0)
+ goto fail;
+ r = snd_mixer_load(mixer);
+ if (r < 0)
+ goto fail;
+ *out = mixer;
+ return 0;
+
+fail:
+ snd_mixer_close(mixer);
+ return -1;
+}
+
+static int
+load_cards(struct card_list *list)
+{
+ int card;
+ int count;
+ int r;
+
+ list->ids = calloc(1, sizeof(list->ids[0]));
+ list->names = calloc(1, sizeof(list->names[0]));
+ if (list->ids == NULL || list->names == NULL)
+ return -1;
+ list->ids[0] = -1;
+ list->names[0] = strdup("default");
+ if (list->names[0] == NULL)
+ return -1;
+ list->n = 1;
+ list->cur = 0;
+
+ card = -1;
+ count = 0;
+ while ((r = snd_card_next(&card)) >= 0 && card >= 0) {
+ char *name;
+ int *ids;
+ char **names;
+
+ name = NULL;
+ if (snd_card_get_name(card, &name) < 0)
+ name = NULL;
+ ids = realloc(list->ids, sizeof(list->ids[0]) * (list->n + 1));
+ names = realloc(list->names, sizeof(list->names[0]) * (list->n + 1));
+ if (ids == NULL || names == NULL)
+ return -1;
+ list->ids = ids;
+ list->names = names;
+ list->ids[list->n] = card;
+ if (name != NULL)
+ list->names[list->n] = name;
+ else
+ list->names[list->n] = strdup("unknown");
+ if (list->names[list->n] == NULL)
+ return -1;
+ list->n++;
+ count++;
+ }
+ if (r < 0)
+ return -1;
+ if (count == 0 && list->n > 0)
+ list->cur = 0;
+ return 0;
+}
+
+static int
+update_card(struct card_list *list, char *buf, size_t len)
+{
+ int id;
+
+ if (list->n == 0)
+ return -1;
+ id = list->ids[list->cur];
+ if (id < 0)
+ xstrlcpy(buf, "default", len);
+ else
+ sprintf(buf, "hw:%d", id);
+ return 0;
+}
+
+static void
+die(const char *msg)
+{
+ err(1, "%s", msg);
+}
+
+int
+main(int argc, char *argv[])
+{
+ snd_mixer_t *mixer;
+ snd_mixer_elem_t *elem;
+ const char *card;
+ int ch;
+ long l;
+
+ progname = argv[0];
+ card = "default";
+
+ while ((ch = getopt(argc, argv, "c:d:glms:t")) != -1) {
+ switch (ch) {
+ case 'c':
+ control = optarg;
+ break;
+ case 'd':
+ card = optarg;
+ break;
+ case 'g':
+ do_get = 1;
+ break;
+ case 'l':
+ do_list = 1;
+ break;
+ case 'm':
+ do_mute = atoi(optarg);
+ if (do_mute != 0 && do_mute != 1)
+ usage();
+ break;
+ case 's':
+ do_set = atoi(optarg);
+ if (do_set < 0 || do_set > 100)
+ usage();
+ break;
+ case 't':
+ do_tui = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (do_list + do_get + (do_set != -1) + (do_mute != -1) + do_tui == 0)
+ do_tui = 1;
+
+ if (open_mixer(&mixer, card) == -1)
+ die("open mixer");
+
+ set_signal_handlers();
+
+ if (do_list) {
+ list_elems(mixer);
+ snd_mixer_close(mixer);
+ return 0;
+ }
+
+ if (do_tui) {
+ snd_mixer_close(mixer);
+ run_tui(&mixer);
+ snd_mixer_close(mixer);
+ return 0;
+ }
+
+ if (control == NULL)
+ usage();
+
+ elem = find_elem(mixer, control);
+ if (elem == NULL)
+ errx(1, "control not found: %s", control);
+
+ if (do_set != -1) {
+ if (set_elem_volume(elem, do_set) == -1)
+ die("set volume");
+ }
+
+ if (do_mute != -1) {
+ if (set_elem_mute(elem, do_mute) == -1)
+ die("set mute");
+ }
+
+ if (do_get) {
+ if (get_elem_volume(elem, &l) == -1)
+ die("get volume");
+ printf("%ld\n", l);
+ }
+
+ snd_mixer_close(mixer);
+ return 0;
+}