575 lines
13 KiB
C
575 lines
13 KiB
C
/* $NetBSD: main.c,v 1.4 2021/07/18 11:45:31 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':
|
|
clear();
|
|
(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 __dead 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();
|
|
use_default_colors();
|
|
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;
|
|
}
|