Module Name: src
Committed By: nia
Date: Fri May 7 16:29:24 UTC 2021
Added Files:
src/usr.bin/aiomixer: Makefile aiomixer.1 app.h draw.c draw.h main.c
parse.c parse.h
Log Message:
import aiomixer from git into usr.bin.
aiomixer is a graphical (curses-based) mixer for NetBSD audio.
To generate a diff of this commit:
cvs rdiff -u -r0 -r1.1 src/usr.bin/aiomixer/Makefile \
src/usr.bin/aiomixer/aiomixer.1 src/usr.bin/aiomixer/app.h \
src/usr.bin/aiomixer/draw.c src/usr.bin/aiomixer/draw.h \
src/usr.bin/aiomixer/main.c src/usr.bin/aiomixer/parse.c \
src/usr.bin/aiomixer/parse.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Added files:
Index: src/usr.bin/aiomixer/Makefile
diff -u /dev/null src/usr.bin/aiomixer/Makefile:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/Makefile Fri May 7 16:29:24 2021
@@ -0,0 +1,11 @@
+# $NetBSD: Makefile,v 1.1 2021/05/07 16:29:24 nia Exp $
+
+PROG= aiomixer
+SRCS+= main.c draw.c parse.c
+
+LDADD+= -lcurses
+DPADD+= $(LIBCURSES)
+
+WARNS= 6
+
+.include <bsd.prog.mk>
Index: src/usr.bin/aiomixer/aiomixer.1
diff -u /dev/null src/usr.bin/aiomixer/aiomixer.1:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/aiomixer.1 Fri May 7 16:29:24 2021
@@ -0,0 +1,90 @@
+.\" $NetBSD: aiomixer.1,v 1.1 2021/05/07 16:29:24 nia Exp $
+.\"
+.\" Copyright (c) 2021 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Nia Alarie.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd April 4, 2019
+.Dt AIOMIXER 1
+.Os
+.Sh NAME
+.Nm aiomixer
+.Nd graphical mixer for
+.Nx
+audio
+.Sh SYNOPSIS
+.Nm aiomixer
+.Op Fl d Ar device
+.Op Fl u
+.Sh DESCRIPTION
+.Nm
+is a graphical frontend for
+.Xr mixer 4
+devices that runs in your terminal.
+.Pp
+.Nm
+is primarily controlled using the cursor keys, e.g. to select a
+control, or change a control's value.
+Movement similar to
+.Xr vi 1
+is also supported.
+The current class can be changed with the number keys.
+The Q or Escape key can be used to return to the previous pane or exit
+.Nm .
+.Pp
+By default, volume levels for individual channels cannot be changed
+separately.
+The U key can be pressed to toggle changing the volume of individual
+channels.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar device
+Used to specify an alternative mixer device.
+.It Fl u
+Used to unlock channels on start up.
+.El
+.Sh FILES
+.Bl -tag -width /dev/mixer[0-3] -compact
+.It Pa /dev/mixer
+.It Pa /dev/mixer[0-3]
+.El
+.Sh SEE ALSO
+.Xr mixerctl 1 ,
+.Xr mixer 4
+.Sh HISTORY
+The first version (only available in pkgsrc) didn't support
+.Dv AUDIO_MIXER_SET
+properly, among other bugs.
+.Sh AUTHORS
+.Nm
+was written by
+.An Nia Alarie
+.Aq [email protected] .
+.Sh BUGS
+.Nm aiomixer
+shows whatever mixer nodes the audio device's driver returns.
+Some drivers do not provide the most user-friendly control names.
Index: src/usr.bin/aiomixer/app.h
diff -u /dev/null src/usr.bin/aiomixer/app.h:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/app.h Fri May 7 16:29:24 2021
@@ -0,0 +1,82 @@
+/* $NetBSD: app.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef APP_H
+#define APP_H
+#include <sys/audioio.h>
+#include <stdbool.h>
+
+enum aiomixer_state {
+ STATE_DEVICE_SELECT,
+ STATE_CLASS_SELECT,
+ STATE_CONTROL_SELECT
+};
+
+struct aiomixer_control {
+ struct mixer_devinfo info;
+ /* currently selected index for sets, channel for sliders */
+ int setindex;
+ int widget_y;
+ int height;
+ WINDOW *widgetpad;
+};
+
+struct aiomixer_class {
+ char name[MAX_AUDIO_DEV_LEN];
+ struct aiomixer_control controls[128];
+ unsigned int numcontrols;
+ WINDOW *widgetpad;
+ int index;
+ int height;
+};
+
+struct aiomixer {
+ int fd;
+ enum aiomixer_state state;
+ struct audio_device mixerdev;
+ struct aiomixer_class classes[128];
+ unsigned int numclasses;
+ unsigned int curclass;
+ unsigned int curcontrol;
+ bool channels_unlocked;
+ int class_scroll_y;
+ int last_max_x;
+ bool widgets_resized;
+ WINDOW *header;
+ WINDOW *classbar;
+};
+
+#define COLOR_CONTROL_SELECTED 1
+#define COLOR_LEVELS 2
+#define COLOR_SET_SELECTED 3
+#define COLOR_ENUM_OFF 4
+#define COLOR_ENUM_ON 5
+#define COLOR_ENUM_MISC 6
+
+#endif
Index: src/usr.bin/aiomixer/draw.c
diff -u /dev/null src/usr.bin/aiomixer/draw.c:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/draw.c Fri May 7 16:29:24 2021
@@ -0,0 +1,416 @@
+/* $NetBSD: draw.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <curses.h>
+#include <err.h>
+#include <stdlib.h>
+#include "draw.h"
+
+static void bold_on(WINDOW *);
+static void bold_off(WINDOW *);
+static int get_enum_color(const char *);
+static void draw_enum(struct aiomixer_control *, int, bool);
+static void draw_set(struct aiomixer_control *, int);
+static void draw_levels(struct aiomixer_control *,
+ const struct mixer_level *, bool, bool);
+
+static void
+bold_on(WINDOW *w)
+{
+ /*
+ * Some (XXX: which?) legacy terminals do not support a Bold
+ * attribute. In this case, we fall back to standout.
+ */
+ if (termattrs() & A_BOLD)
+ wattron(w, A_BOLD);
+ else
+ wattron(w, A_STANDOUT);
+}
+
+static void
+bold_off(WINDOW *w)
+{
+ chtype attrs = getattrs(w);
+
+ if (attrs & A_BOLD)
+ wattroff(w, A_BOLD);
+ if (attrs & A_STANDOUT)
+ wattroff(w, A_STANDOUT);
+}
+
+void
+draw_mixer_select(unsigned int num_mixers, unsigned int selected_mixer)
+{
+ struct audio_device dev;
+ char mixer_path[16];
+ unsigned int i;
+ int fd;
+
+ mvprintw(0, 0, "Select a mixer device:\n");
+
+ for (i = 0; i < num_mixers; ++i) {
+ (void)snprintf(mixer_path, sizeof(mixer_path),
+ "/dev/mixer%d", i);
+ fd = open(mixer_path, O_RDWR);
+ if (fd == -1)
+ break;
+ if (ioctl(fd, AUDIO_GETDEV, &dev) < 0) {
+ close(fd);
+ break;
+ }
+ close(fd);
+ if (selected_mixer == i) {
+ bold_on(stdscr);
+ addstr("[*] ");
+ } else {
+ addstr("[ ] ");
+ }
+ printw("%s: %s %s %s\n", mixer_path,
+ dev.name, dev.version, dev.config);
+ if (selected_mixer == i)
+ bold_off(stdscr);
+ }
+}
+
+void
+draw_control(struct aiomixer *aio,
+ struct aiomixer_control *control, bool selected)
+{
+ struct mixer_ctrl value;
+
+ value.dev = control->info.index;
+ value.type = control->info.type;
+ if (value.type == AUDIO_MIXER_VALUE)
+ value.un.value.num_channels = control->info.un.v.num_channels;
+
+ if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
+ err(EXIT_FAILURE, "failed to read from mixer device");
+
+ wclear(control->widgetpad);
+ if (selected) {
+ bold_on(control->widgetpad);
+ if (has_colors()) {
+ wattron(control->widgetpad,
+ COLOR_PAIR(COLOR_CONTROL_SELECTED));
+ }
+ waddch(control->widgetpad, '*');
+ if (has_colors()) {
+ wattroff(control->widgetpad,
+ COLOR_PAIR(COLOR_CONTROL_SELECTED));
+ }
+ }
+ wprintw(control->widgetpad, "%s\n", control->info.label.name);
+ if (selected)
+ bold_off(control->widgetpad);
+
+ switch (value.type) {
+ case AUDIO_MIXER_ENUM:
+ draw_enum(control, value.un.ord, selected);
+ break;
+ case AUDIO_MIXER_SET:
+ draw_set(control, value.un.mask);
+ break;
+ case AUDIO_MIXER_VALUE:
+ draw_levels(control, &value.un.value,
+ aio->channels_unlocked, selected);
+ break;
+ }
+}
+
+void
+draw_screen(struct aiomixer *aio)
+{
+ int i, max_y;
+
+ /* Clear any leftovers if the screen changed. */
+ if (aio->widgets_resized) {
+ aio->widgets_resized = false;
+ max_y = getmaxy(stdscr);
+ for (i = 3; i < max_y; ++i) {
+ mvprintw(i, 0, "\n");
+ }
+ }
+
+ wnoutrefresh(stdscr);
+ wnoutrefresh(aio->header);
+ wnoutrefresh(aio->classbar);
+ pnoutrefresh(aio->classes[aio->curclass].widgetpad,
+ aio->class_scroll_y, 0,
+ 3, 0,
+ 1 + aio->classes[aio->curclass].height, getmaxx(stdscr));
+ doupdate();
+}
+
+static int
+get_enum_color(const char *name)
+{
+ if (strcmp(name, AudioNon) == 0) {
+ return COLOR_ENUM_ON;
+ }
+ if (strcmp(name, AudioNoff) == 0) {
+ return COLOR_ENUM_OFF;
+ }
+
+ return COLOR_ENUM_MISC;
+}
+
+static void
+draw_enum(struct aiomixer_control *control, int ord, bool selected)
+{
+ struct audio_mixer_enum *e;
+ int color = COLOR_ENUM_MISC;
+ int i;
+
+ for (i = 0; i < control->info.un.e.num_mem; ++i) {
+ e = &control->info.un.e;
+ if (ord == e->member[i].ord && selected)
+ bold_on(control->widgetpad);
+ waddch(control->widgetpad, '[');
+ if (ord == e->member[i].ord) {
+ if (has_colors()) {
+ color = get_enum_color(e->member[i].label.name);
+ wattron(control->widgetpad,
+ COLOR_PAIR(color));
+ } else {
+ waddch(control->widgetpad, '*');
+ }
+ }
+ wprintw(control->widgetpad, "%s", e->member[i].label.name);
+ if (ord == control->info.un.e.member[i].ord) {
+ if (has_colors()) {
+ wattroff(control->widgetpad,
+ COLOR_PAIR(color));
+ }
+ }
+ waddch(control->widgetpad, ']');
+ if (ord == e->member[i].ord && selected)
+ bold_off(control->widgetpad);
+ if (i != (e->num_mem - 1))
+ waddstr(control->widgetpad, ", ");
+ }
+ waddch(control->widgetpad, '\n');
+}
+
+static void
+draw_set(struct aiomixer_control *control, int mask)
+{
+ int i;
+
+ for (i = 0; i < control->info.un.s.num_mem; ++i) {
+ waddch(control->widgetpad, '[');
+ if (mask & control->info.un.s.member[i].mask) {
+ if (has_colors()) {
+ wattron(control->widgetpad,
+ COLOR_PAIR(COLOR_SET_SELECTED));
+ }
+ waddch(control->widgetpad, '*');
+ if (has_colors()) {
+ wattroff(control->widgetpad,
+ COLOR_PAIR(COLOR_SET_SELECTED));
+ }
+ } else {
+ waddch(control->widgetpad, ' ');
+ }
+ waddstr(control->widgetpad, "] ");
+ if (control->setindex == i) {
+ bold_on(control->widgetpad);
+ waddch(control->widgetpad, '*');
+ }
+ wprintw(control->widgetpad, "%s",
+ control->info.un.s.member[i].label.name);
+ if (control->setindex == i)
+ bold_off(control->widgetpad);
+ if (i != (control->info.un.s.num_mem - 1))
+ waddstr(control->widgetpad, ", ");
+ }
+}
+
+static void
+draw_levels(struct aiomixer_control *control,
+ const struct mixer_level *levels, bool channels_unlocked, bool selected)
+{
+ int i;
+ int j, nchars;
+
+ for (i = 0; i < control->info.un.v.num_channels; ++i) {
+ if ((selected && !channels_unlocked) ||
+ (control->setindex == i && channels_unlocked)) {
+ bold_on(control->widgetpad);
+ }
+ wprintw(control->widgetpad, "[%3u/%3u ",
+ levels->level[i], AUDIO_MAX_GAIN);
+ if (has_colors()) {
+ wattron(control->widgetpad,
+ COLOR_PAIR(COLOR_LEVELS));
+ }
+ nchars = (levels->level[i] *
+ (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN;
+ for (j = 0; j < nchars; ++j)
+ waddch(control->widgetpad, '*');
+ if (has_colors()) {
+ wattroff(control->widgetpad,
+ COLOR_PAIR(COLOR_LEVELS));
+ }
+ nchars = getmaxx(control->widgetpad) - 11 - nchars;
+ for (j = 0; j < nchars; ++j)
+ waddch(control->widgetpad, ' ');
+ wprintw(control->widgetpad, "]\n");
+ if ((selected && !channels_unlocked) ||
+ (control->setindex == i && channels_unlocked)) {
+ bold_off(control->widgetpad);
+ }
+ }
+}
+
+void
+draw_classbar(struct aiomixer *aio)
+{
+ unsigned int i;
+
+ wmove(aio->classbar, 0, 0);
+
+ for (i = 0; i < aio->numclasses; ++i) {
+ if (aio->curclass == i)
+ bold_on(aio->classbar);
+ wprintw(aio->classbar, "[%u:", i + 1);
+ if (aio->curclass == i) {
+ if (has_colors()) {
+ wattron(aio->classbar,
+ COLOR_PAIR(COLOR_CONTROL_SELECTED));
+ }
+ waddch(aio->classbar, '*');
+ if (has_colors()) {
+ wattroff(aio->classbar,
+ COLOR_PAIR(COLOR_CONTROL_SELECTED));
+ }
+ }
+ waddstr(aio->classbar, aio->classes[i].name);
+ if (aio->curclass == i)
+ bold_off(aio->classbar);
+ waddstr(aio->classbar, "] ");
+ }
+
+ wprintw(aio->classbar, "\n\n");
+}
+
+void
+draw_header(struct aiomixer *aio)
+{
+ mvwaddstr(aio->header, 0,
+ getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer") + 1,
+ "NetBSD audio mixer");
+
+ if (aio->mixerdev.version[0] != '\0') {
+ wprintw(aio->header, "%s %s",
+ aio->mixerdev.name, aio->mixerdev.version);
+ } else {
+ wprintw(aio->header, "%s", aio->mixerdev.name);
+ }
+}
+
+void
+create_widgets(struct aiomixer *aio)
+{
+ size_t i, j;
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+
+ aio->header = newwin(1, getmaxx(stdscr), 0, 0);
+ if (aio->header == NULL)
+ errx(EXIT_FAILURE, "failed to create window");
+
+ aio->classbar = newwin(2, getmaxx(stdscr), 1, 0);
+ if (aio->classbar == NULL)
+ errx(EXIT_FAILURE, "failed to create window");
+
+ for (i = 0; i < aio->numclasses; ++i) {
+ class = &aio->classes[i];
+ class->height = 0;
+ class->widgetpad = newpad(4 * __arraycount(class->controls),
+ getmaxx(stdscr));
+ if (class->widgetpad == NULL)
+ errx(EXIT_FAILURE, "failed to create curses pad");
+ for (j = 0; j < class->numcontrols; ++j) {
+ control = &class->controls[j];
+ switch (control->info.type) {
+ case AUDIO_MIXER_VALUE:
+ control->height = 2 +
+ control->info.un.v.num_channels;
+ break;
+ case AUDIO_MIXER_ENUM:
+ case AUDIO_MIXER_SET:
+ control->height = 3;
+ break;
+ }
+ control->widgetpad = subpad(class->widgetpad,
+ control->height, getmaxx(stdscr),
+ class->height, 0);
+ if (control->widgetpad == NULL)
+ errx(EXIT_FAILURE, "failed to create curses pad");
+ control->widget_y = class->height;
+ class->height += control->height;
+ }
+ wresize(class->widgetpad, class->height, getmaxx(stdscr));
+ }
+
+ aio->last_max_x = getmaxx(stdscr);
+}
+
+void
+resize_widgets(struct aiomixer *aio)
+{
+ size_t i, j;
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+ int max_x;
+
+ max_x = getmaxx(stdscr);
+
+ if (aio->last_max_x != max_x) {
+ aio->last_max_x = max_x;
+ wresize(aio->header, 1, max_x);
+ wresize(aio->classbar, 2, max_x);
+
+ for (i = 0; i < aio->numclasses; ++i) {
+ class = &aio->classes[i];
+ wresize(class->widgetpad, class->height, max_x);
+ for (j = 0; j < class->numcontrols; ++j) {
+ control = &class->controls[j];
+ wresize(control->widgetpad,
+ control->height, max_x);
+ }
+ }
+ }
+
+ aio->widgets_resized = true;
+}
Index: src/usr.bin/aiomixer/draw.h
diff -u /dev/null src/usr.bin/aiomixer/draw.h:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/draw.h Fri May 7 16:29:24 2021
@@ -0,0 +1,51 @@
+/* $NetBSD: draw.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef DRAW_H
+#define DRAW_H
+
+#include <sys/audioio.h>
+#include <stdbool.h>
+#include "app.h"
+
+void draw_mixer_select(unsigned int, unsigned int);
+
+void create_widgets(struct aiomixer *);
+
+void resize_widgets(struct aiomixer *);
+
+void draw_control(struct aiomixer *, struct aiomixer_control *, bool);
+
+void draw_screen(struct aiomixer *);
+
+void draw_classbar(struct aiomixer *);
+
+void draw_header(struct aiomixer *);
+
+#endif
Index: src/usr.bin/aiomixer/main.c
diff -u /dev/null src/usr.bin/aiomixer/main.c:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/main.c Fri May 7 16:29:24 2021
@@ -0,0 +1,572 @@
+/* $NetBSD: main.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <paths.h>
+#include <curses.h>
+#include <stdlib.h>
+#include <err.h>
+#include "app.h"
+#include "draw.h"
+#include "parse.h"
+
+static void process_device_select(struct aiomixer *, unsigned int);
+static void open_device(struct aiomixer *, const char *);
+static void __dead usage(void);
+static int adjust_level(int, int);
+static int select_class(struct aiomixer *, unsigned int);
+static int select_control(struct aiomixer *, unsigned int);
+static void slide_control(struct aiomixer *, struct aiomixer_control *, bool);
+static int toggle_set(struct aiomixer *);
+static void step_up(struct aiomixer *);
+static void step_down(struct aiomixer *);
+static int read_key(struct aiomixer *, int);
+
+static void __dead
+usage(void)
+{
+ fputs("aiomixer [-u] [-d device]\n", stderr);
+ exit(1);
+}
+
+static int
+select_class(struct aiomixer *aio, unsigned int n)
+{
+ struct aiomixer_class *class;
+ unsigned i;
+
+ if (n >= aio->numclasses)
+ return -1;
+
+ class = &aio->classes[n];
+ aio->widgets_resized = true;
+ aio->class_scroll_y = 0;
+ aio->curcontrol = 0;
+ aio->curclass = n;
+ for (i = 0; i < class->numcontrols; ++i) {
+ class->controls[i].setindex = -1;
+ draw_control(aio, &class->controls[i], false);
+ }
+ draw_classbar(aio);
+ return 0;
+}
+
+static int
+select_control(struct aiomixer *aio, unsigned int n)
+{
+ struct aiomixer_class *class;
+ struct aiomixer_control *lastcontrol;
+ struct aiomixer_control *control;
+
+ class = &aio->classes[aio->curclass];
+
+ if (n >= class->numcontrols)
+ return -1;
+
+ lastcontrol = &class->controls[aio->curcontrol];
+ lastcontrol->setindex = -1;
+ draw_control(aio, lastcontrol, false);
+
+ control = &class->controls[n];
+ aio->curcontrol = n;
+ control->setindex = 0;
+ draw_control(aio, control, true);
+
+ if (aio->class_scroll_y > control->widget_y) {
+ aio->class_scroll_y = control->widget_y;
+ aio->widgets_resized = true;
+ }
+
+ if ((control->widget_y + control->height) >
+ ((getmaxy(stdscr) - 4) + aio->class_scroll_y)) {
+ aio->class_scroll_y = control->widget_y;
+ aio->widgets_resized = true;
+ }
+ return 0;
+}
+
+static int
+adjust_level(int level, int delta)
+{
+ if (level > (AUDIO_MAX_GAIN - delta))
+ return AUDIO_MAX_GAIN;
+
+ if (delta < 0 && level < (AUDIO_MIN_GAIN + (-delta)))
+ return AUDIO_MIN_GAIN;
+
+ return level + delta;
+}
+
+static void
+slide_control(struct aiomixer *aio,
+ struct aiomixer_control *control, bool right)
+{
+ struct mixer_devinfo *info = &control->info;
+ struct mixer_ctrl value;
+ unsigned char *level;
+ int i, delta;
+ int cur_index = 0;
+
+ if (info->type != AUDIO_MIXER_SET) {
+ value.dev = info->index;
+ value.type = info->type;
+ if (info->type == AUDIO_MIXER_VALUE)
+ value.un.value.num_channels = info->un.v.num_channels;
+
+ if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
+ err(EXIT_FAILURE, "failed to read mixer control");
+ }
+
+ switch (info->type) {
+ case AUDIO_MIXER_VALUE:
+ delta = right ? info->un.v.delta : -info->un.v.delta;
+ /*
+ * work around strange problem where the level can be
+ * increased but not decreased, seen with uaudio(4)
+ */
+ if (delta < 16)
+ delta *= 2;
+ if (aio->channels_unlocked) {
+ level = &value.un.value.level[control->setindex];
+ *level = (unsigned char)adjust_level(*level, delta);
+ } else {
+ for (i = 0; i < value.un.value.num_channels; ++i) {
+ level = &value.un.value.level[i];
+ *level = (unsigned char)adjust_level(*level, delta);
+ }
+ }
+ break;
+ case AUDIO_MIXER_ENUM:
+ for (i = 0; i < info->un.e.num_mem; ++i) {
+ if (info->un.e.member[i].ord == value.un.ord) {
+ cur_index = i;
+ break;
+ }
+ }
+ if (right) {
+ value.un.ord = cur_index < (info->un.e.num_mem - 1) ?
+ info->un.e.member[cur_index + 1].ord :
+ info->un.e.member[0].ord;
+ } else {
+ value.un.ord = cur_index > 0 ?
+ info->un.e.member[cur_index - 1].ord :
+ info->un.e.member[control->info.un.e.num_mem - 1].ord;
+ }
+ break;
+ case AUDIO_MIXER_SET:
+ if (right) {
+ control->setindex =
+ control->setindex < (info->un.s.num_mem - 1) ?
+ control->setindex + 1 : 0;
+ } else {
+ control->setindex = control->setindex > 0 ?
+ control->setindex - 1 :
+ control->info.un.s.num_mem - 1;
+ }
+ break;
+ }
+
+ if (info->type != AUDIO_MIXER_SET) {
+ if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &value) < 0)
+ err(EXIT_FAILURE, "failed to adjust mixer control");
+ }
+
+ draw_control(aio, control, true);
+}
+
+static int
+toggle_set(struct aiomixer *aio)
+{
+ struct mixer_ctrl ctrl;
+ struct aiomixer_class *class = &aio->classes[aio->curclass];
+ struct aiomixer_control *control = &class->controls[aio->curcontrol];
+
+ ctrl.dev = control->info.index;
+ ctrl.type = control->info.type;
+
+ if (control->info.type != AUDIO_MIXER_SET)
+ return -1;
+
+ if (ioctl(aio->fd, AUDIO_MIXER_READ, &ctrl) < 0)
+ err(EXIT_FAILURE, "failed to read mixer control");
+
+ ctrl.un.mask ^= control->info.un.s.member[control->setindex].mask;
+
+ if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &ctrl) < 0)
+ err(EXIT_FAILURE, "failed to read mixer control");
+
+ draw_control(aio, control, true);
+ return 0;
+}
+
+static void
+step_up(struct aiomixer *aio)
+{
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+
+ class = &aio->classes[aio->curclass];
+ control = &class->controls[aio->curcontrol];
+
+ if (aio->channels_unlocked &&
+ control->info.type == AUDIO_MIXER_VALUE &&
+ control->setindex > 0) {
+ control->setindex--;
+ draw_control(aio, control, true);
+ return;
+ }
+ select_control(aio, aio->curcontrol - 1);
+}
+
+static void
+step_down(struct aiomixer *aio)
+{
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+
+ class = &aio->classes[aio->curclass];
+ control = &class->controls[aio->curcontrol];
+
+ if (aio->channels_unlocked &&
+ control->info.type == AUDIO_MIXER_VALUE &&
+ control->setindex < (control->info.un.v.num_channels - 1)) {
+ control->setindex++;
+ draw_control(aio, control, true);
+ return;
+ }
+
+ select_control(aio, (aio->curcontrol + 1) % class->numcontrols);
+}
+
+static int
+read_key(struct aiomixer *aio, int ch)
+{
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+ size_t i;
+
+ switch (ch) {
+ case KEY_RESIZE:
+ class = &aio->classes[aio->curclass];
+ resize_widgets(aio);
+ draw_header(aio);
+ draw_classbar(aio);
+ for (i = 0; i < class->numcontrols; ++i) {
+ draw_control(aio,
+ &class->controls[i],
+ aio->state == STATE_CONTROL_SELECT ?
+ (aio->curcontrol == i) : false);
+ }
+ break;
+ case KEY_LEFT:
+ case 'h':
+ if (aio->state == STATE_CLASS_SELECT) {
+ select_class(aio, aio->curclass > 0 ?
+ aio->curclass - 1 : aio->numclasses - 1);
+ } else if (aio->state == STATE_CONTROL_SELECT) {
+ class = &aio->classes[aio->curclass];
+ slide_control(aio,
+ &class->controls[aio->curcontrol], false);
+ }
+ break;
+ case KEY_RIGHT:
+ case 'l':
+ if (aio->state == STATE_CLASS_SELECT) {
+ select_class(aio,
+ (aio->curclass + 1) % aio->numclasses);
+ } else if (aio->state == STATE_CONTROL_SELECT) {
+ class = &aio->classes[aio->curclass];
+ slide_control(aio,
+ &class->controls[aio->curcontrol], true);
+ }
+ break;
+ case KEY_UP:
+ case 'k':
+ if (aio->state == STATE_CONTROL_SELECT) {
+ if (aio->curcontrol == 0) {
+ class = &aio->classes[aio->curclass];
+ control = &class->controls[aio->curcontrol];
+ control->setindex = -1;
+ aio->state = STATE_CLASS_SELECT;
+ draw_control(aio, control, false);
+ } else {
+ step_up(aio);
+ }
+ }
+ break;
+ case KEY_DOWN:
+ case 'j':
+ if (aio->state == STATE_CLASS_SELECT) {
+ class = &aio->classes[aio->curclass];
+ if (class->numcontrols > 0) {
+ aio->state = STATE_CONTROL_SELECT;
+ select_control(aio, 0);
+ }
+ } else if (aio->state == STATE_CONTROL_SELECT) {
+ step_down(aio);
+ }
+ break;
+ case '\n':
+ case ' ':
+ if (aio->state == STATE_CONTROL_SELECT)
+ toggle_set(aio);
+ break;
+ case '1':
+ select_class(aio, 0);
+ break;
+ case '2':
+ select_class(aio, 1);
+ break;
+ case '3':
+ select_class(aio, 2);
+ break;
+ case '4':
+ select_class(aio, 3);
+ break;
+ case '5':
+ select_class(aio, 4);
+ break;
+ case '6':
+ select_class(aio, 5);
+ break;
+ case '7':
+ select_class(aio, 6);
+ break;
+ case '8':
+ select_class(aio, 7);
+ break;
+ case '9':
+ select_class(aio, 8);
+ break;
+ case 'q':
+ case '\e':
+ if (aio->state == STATE_CONTROL_SELECT) {
+ class = &aio->classes[aio->curclass];
+ control = &class->controls[aio->curcontrol];
+ aio->state = STATE_CLASS_SELECT;
+ draw_control(aio, control, false);
+ break;
+ }
+ return 1;
+ case 'u':
+ aio->channels_unlocked = !aio->channels_unlocked;
+ if (aio->state == STATE_CONTROL_SELECT) {
+ class = &aio->classes[aio->curclass];
+ control = &class->controls[aio->curcontrol];
+ if (control->info.type == AUDIO_MIXER_VALUE)
+ draw_control(aio, control, true);
+ }
+ break;
+ }
+
+ draw_screen(aio);
+ return 0;
+}
+
+static void
+process_device_select(struct aiomixer *aio, unsigned int num_devices)
+{
+ unsigned int selected_device = 0;
+ char device_path[16];
+ int ch;
+
+ draw_mixer_select(num_devices, selected_device);
+
+ while ((ch = getch()) != ERR) {
+ switch (ch) {
+ case '\n':
+ (void)snprintf(device_path, sizeof(device_path),
+ "/dev/mixer%d", selected_device);
+ open_device(aio, device_path);
+ return;
+ case KEY_UP:
+ case 'k':
+ if (selected_device > 0)
+ selected_device--;
+ else
+ selected_device = (num_devices - 1);
+ break;
+ case KEY_DOWN:
+ case 'j':
+ if (selected_device < (num_devices - 1))
+ selected_device++;
+ else
+ selected_device = 0;
+ break;
+ case '1':
+ selected_device = 0;
+ break;
+ case '2':
+ selected_device = 1;
+ break;
+ case '3':
+ selected_device = 2;
+ break;
+ case '4':
+ selected_device = 3;
+ break;
+ case '5':
+ selected_device = 4;
+ break;
+ case '6':
+ selected_device = 5;
+ break;
+ case '7':
+ selected_device = 6;
+ break;
+ case '8':
+ selected_device = 7;
+ break;
+ case '9':
+ selected_device = 8;
+ break;
+ }
+ draw_mixer_select(num_devices, selected_device);
+ }
+}
+
+static void
+open_device(struct aiomixer *aio, const char *device)
+{
+ int ch;
+
+ if ((aio->fd = open(device, O_RDWR)) < 0)
+ err(EXIT_FAILURE, "couldn't open mixer device");
+
+ if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0)
+ err(EXIT_FAILURE, "AUDIO_GETDEV failed");
+
+ aio->state = STATE_CLASS_SELECT;
+
+ aiomixer_parse(aio);
+
+ create_widgets(aio);
+
+ draw_header(aio);
+ select_class(aio, 0);
+ draw_screen(aio);
+
+ while ((ch = getch()) != ERR) {
+ if (read_key(aio, ch) != 0)
+ break;
+ }
+}
+
+static void
+on_signal(int dummy)
+{
+ endwin();
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *mixer_device = NULL;
+ extern char *optarg;
+ extern int optind;
+ struct aiomixer *aio;
+ char mixer_path[32];
+ unsigned int mixer_count = 0;
+ int i, fd;
+ int ch;
+
+ if ((aio = malloc(sizeof(struct aiomixer))) == NULL) {
+ err(EXIT_FAILURE, "malloc failed");
+ }
+
+ while ((ch = getopt(argc, argv, "d:u")) != -1) {
+ switch (ch) {
+ case 'd':
+ mixer_device = optarg;
+ break;
+ case 'u':
+ aio->channels_unlocked = true;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (initscr() == NULL)
+ err(EXIT_FAILURE, "can't initialize curses");
+
+ (void)signal(SIGHUP, on_signal);
+ (void)signal(SIGINT, on_signal);
+ (void)signal(SIGTERM, on_signal);
+
+ curs_set(0);
+ keypad(stdscr, TRUE);
+ cbreak();
+ noecho();
+
+ if (has_colors()) {
+ start_color();
+ init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK);
+ init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK);
+ init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN);
+ init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED);
+ init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE);
+ init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW);
+ }
+
+ if (mixer_device != NULL) {
+ open_device(aio, mixer_device);
+ } else {
+ for (i = 0; i < 16; ++i) {
+ (void)snprintf(mixer_path, sizeof(mixer_path),
+ "/dev/mixer%d", i);
+ fd = open(mixer_path, O_RDWR);
+ if (fd == -1)
+ break;
+ close(fd);
+ mixer_count++;
+ }
+
+ if (mixer_count > 1) {
+ process_device_select(aio, mixer_count);
+ } else {
+ open_device(aio, _PATH_MIXER);
+ }
+ }
+
+ endwin();
+ close(aio->fd);
+ free(aio);
+
+ return 0;
+}
Index: src/usr.bin/aiomixer/parse.c
diff -u /dev/null src/usr.bin/aiomixer/parse.c:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/parse.c Fri May 7 16:29:24 2021
@@ -0,0 +1,115 @@
+/* $NetBSD: parse.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <curses.h>
+#include <stdlib.h>
+#include "app.h"
+#include "parse.h"
+
+static struct aiomixer_class *get_class(struct aiomixer *, int);
+static int compare_control(const void *, const void *);
+
+static struct aiomixer_class *
+get_class(struct aiomixer *aio, int class)
+{
+ size_t i;
+
+ for (i = 0; i < aio->numclasses; ++i) {
+ if (aio->classes[i].index == class) {
+ return &aio->classes[i];
+ }
+ }
+ return NULL;
+}
+
+static int
+compare_control(const void *pa, const void *pb)
+{
+ const struct aiomixer_control *a = (const struct aiomixer_control *)pa;
+ const struct aiomixer_control *b = (const struct aiomixer_control *)pb;
+
+ if (a->info.prev != AUDIO_MIXER_LAST &&
+ b->info.prev != AUDIO_MIXER_LAST) {
+ if (b->info.prev == a->info.index)
+ return -1;
+ if (a->info.prev == b->info.index)
+ return 1;
+ }
+ return strcmp(a->info.label.name, b->info.label.name);
+}
+
+int
+aiomixer_parse(struct aiomixer *aio)
+{
+ size_t i;
+ struct mixer_devinfo info;
+ struct aiomixer_class *class;
+ struct aiomixer_control *control;
+
+ for (info.index = 0;
+ ioctl(aio->fd, AUDIO_MIXER_DEVINFO, &info) != -1; ++info.index) {
+ if (info.type != AUDIO_MIXER_CLASS)
+ continue;
+ if (aio->numclasses >= __arraycount(aio->classes))
+ break;
+ class = &aio->classes[aio->numclasses++];
+ memcpy(class->name, info.label.name, MAX_AUDIO_DEV_LEN);
+ class->index = info.index;
+ class->numcontrols = 0;
+ }
+ for (info.index = 0;
+ ioctl(aio->fd, AUDIO_MIXER_DEVINFO, &info) != -1; ++info.index) {
+ if (info.type == AUDIO_MIXER_CLASS)
+ continue;
+ if (info.type == AUDIO_MIXER_VALUE) {
+ /* XXX workaround for hdaudio(4) bugs */
+ if (info.un.v.delta > AUDIO_MAX_GAIN)
+ continue;
+ if (info.un.v.num_channels == 0)
+ continue;
+ }
+ class = get_class(aio, info.mixer_class);
+ if (class == NULL)
+ break;
+ if (class->numcontrols >= __arraycount(class->controls))
+ break;
+ control = &class->controls[class->numcontrols++];
+ control->info = info;
+ }
+ for (i = 0; i < aio->numclasses; ++i) {
+ class = &aio->classes[i];
+ qsort(class->controls, class->numcontrols,
+ sizeof(struct aiomixer_control),
+ compare_control);
+ }
+ return 0;
+}
+
Index: src/usr.bin/aiomixer/parse.h
diff -u /dev/null src/usr.bin/aiomixer/parse.h:1.1
--- /dev/null Fri May 7 16:29:24 2021
+++ src/usr.bin/aiomixer/parse.h Fri May 7 16:29:24 2021
@@ -0,0 +1,37 @@
+/* $NetBSD: parse.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef PARSE_H
+#define PARSE_H
+
+#include "app.h"
+
+int aiomixer_parse(struct aiomixer *);
+
+#endif