#include #include #include #include #include #include #include #include #include #include #include #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; }