xref: /netbsd-src/usr.bin/aiomixer/main.c (revision 33b82b03392e2106db8728db8eea7f5301157325)
1*33b82b03Srillig /* $NetBSD: main.c,v 1.7 2024/11/03 10:43:27 rillig Exp $ */
226802ffdSnia /*-
326802ffdSnia  * Copyright (c) 2021 The NetBSD Foundation, Inc.
426802ffdSnia  * All rights reserved.
526802ffdSnia  *
626802ffdSnia  * This code is derived from software contributed to The NetBSD Foundation
726802ffdSnia  * by Nia Alarie.
826802ffdSnia  *
926802ffdSnia  * Redistribution and use in source and binary forms, with or without
1026802ffdSnia  * modification, are permitted provided that the following conditions
1126802ffdSnia  * are met:
1226802ffdSnia  * 1. Redistributions of source code must retain the above copyright
1326802ffdSnia  *    notice, this list of conditions and the following disclaimer.
1426802ffdSnia  * 2. Redistributions in binary form must reproduce the above copyright
1526802ffdSnia  *    notice, this list of conditions and the following disclaimer in the
1626802ffdSnia  *    documentation and/or other materials provided with the distribution.
1726802ffdSnia  *
1826802ffdSnia  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
1926802ffdSnia  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2026802ffdSnia  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2126802ffdSnia  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2226802ffdSnia  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2326802ffdSnia  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2426802ffdSnia  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2526802ffdSnia  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2626802ffdSnia  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2726802ffdSnia  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2826802ffdSnia  * POSSIBILITY OF SUCH DAMAGE.
2926802ffdSnia  */
3026802ffdSnia #include <sys/audioio.h>
3126802ffdSnia #include <sys/ioctl.h>
3226802ffdSnia #include <fcntl.h>
3326802ffdSnia #include <unistd.h>
3426802ffdSnia #include <signal.h>
3526802ffdSnia #include <paths.h>
3626802ffdSnia #include <curses.h>
3726802ffdSnia #include <stdlib.h>
3826802ffdSnia #include <err.h>
3926802ffdSnia #include "app.h"
4026802ffdSnia #include "draw.h"
4126802ffdSnia #include "parse.h"
4226802ffdSnia 
4326802ffdSnia static void process_device_select(struct aiomixer *, unsigned int);
4426802ffdSnia static void open_device(struct aiomixer *, const char *);
4526802ffdSnia static void __dead usage(void);
4626802ffdSnia static int adjust_level(int, int);
4726802ffdSnia static int select_class(struct aiomixer *, unsigned int);
4826802ffdSnia static int select_control(struct aiomixer *, unsigned int);
4926802ffdSnia static void slide_control(struct aiomixer *, struct aiomixer_control *, bool);
5026802ffdSnia static int toggle_set(struct aiomixer *);
5126802ffdSnia static void step_up(struct aiomixer *);
5226802ffdSnia static void step_down(struct aiomixer *);
5326802ffdSnia static int read_key(struct aiomixer *, int);
5426802ffdSnia 
5526802ffdSnia static void __dead
5626802ffdSnia usage(void)
5726802ffdSnia {
5826802ffdSnia 	fputs("aiomixer [-u] [-d device]\n", stderr);
5926802ffdSnia 	exit(1);
6026802ffdSnia }
6126802ffdSnia 
6226802ffdSnia static int
6326802ffdSnia select_class(struct aiomixer *aio, unsigned int n)
6426802ffdSnia {
6526802ffdSnia 	struct aiomixer_class *class;
6626802ffdSnia 	unsigned i;
6726802ffdSnia 
6826802ffdSnia 	if (n >= aio->numclasses)
6926802ffdSnia 		return -1;
7026802ffdSnia 
7126802ffdSnia 	class = &aio->classes[n];
7226802ffdSnia 	aio->widgets_resized = true;
7326802ffdSnia 	aio->class_scroll_y = 0;
7426802ffdSnia 	aio->curcontrol = 0;
7526802ffdSnia 	aio->curclass = n;
7626802ffdSnia 	for (i = 0; i < class->numcontrols; ++i) {
7726802ffdSnia 		class->controls[i].setindex = -1;
7826802ffdSnia 		draw_control(aio, &class->controls[i], false);
7926802ffdSnia 	}
8026802ffdSnia 	draw_classbar(aio);
8126802ffdSnia 	return 0;
8226802ffdSnia }
8326802ffdSnia 
8426802ffdSnia static int
8526802ffdSnia select_control(struct aiomixer *aio, unsigned int n)
8626802ffdSnia {
8726802ffdSnia 	struct aiomixer_class *class;
8826802ffdSnia 	struct aiomixer_control *lastcontrol;
8926802ffdSnia 	struct aiomixer_control *control;
9026802ffdSnia 
9126802ffdSnia 	class = &aio->classes[aio->curclass];
9226802ffdSnia 
9326802ffdSnia 	if (n >= class->numcontrols)
9426802ffdSnia 		return -1;
9526802ffdSnia 
9626802ffdSnia 	lastcontrol = &class->controls[aio->curcontrol];
9726802ffdSnia 	lastcontrol->setindex = -1;
9826802ffdSnia 	draw_control(aio, lastcontrol, false);
9926802ffdSnia 
10026802ffdSnia 	control = &class->controls[n];
10126802ffdSnia 	aio->curcontrol = n;
10226802ffdSnia 	control->setindex = 0;
10326802ffdSnia 	draw_control(aio, control, true);
10426802ffdSnia 
10526802ffdSnia 	if (aio->class_scroll_y > control->widget_y) {
10626802ffdSnia 		aio->class_scroll_y = control->widget_y;
10726802ffdSnia 		aio->widgets_resized = true;
10826802ffdSnia 	}
10926802ffdSnia 
11026802ffdSnia 	if ((control->widget_y + control->height) >
11126802ffdSnia 	    ((getmaxy(stdscr) - 4) + aio->class_scroll_y)) {
11226802ffdSnia 		aio->class_scroll_y = control->widget_y;
11326802ffdSnia 		aio->widgets_resized = true;
11426802ffdSnia 	}
11526802ffdSnia 	return 0;
11626802ffdSnia }
11726802ffdSnia 
11826802ffdSnia static int
11926802ffdSnia adjust_level(int level, int delta)
12026802ffdSnia {
12126802ffdSnia 	if (level > (AUDIO_MAX_GAIN - delta))
12226802ffdSnia 		return AUDIO_MAX_GAIN;
12326802ffdSnia 
12426802ffdSnia 	if (delta < 0 && level < (AUDIO_MIN_GAIN + (-delta)))
12526802ffdSnia 		return AUDIO_MIN_GAIN;
12626802ffdSnia 
12726802ffdSnia 	return level + delta;
12826802ffdSnia }
12926802ffdSnia 
13026802ffdSnia static void
13126802ffdSnia slide_control(struct aiomixer *aio,
13226802ffdSnia     struct aiomixer_control *control, bool right)
13326802ffdSnia {
13426802ffdSnia 	struct mixer_devinfo *info = &control->info;
13526802ffdSnia 	struct mixer_ctrl value;
13626802ffdSnia 	unsigned char *level;
13726802ffdSnia 	int i, delta;
13826802ffdSnia 	int cur_index = 0;
13926802ffdSnia 
14026802ffdSnia 	if (info->type != AUDIO_MIXER_SET) {
14126802ffdSnia 		value.dev = info->index;
14226802ffdSnia 		value.type = info->type;
14326802ffdSnia 		if (info->type == AUDIO_MIXER_VALUE)
14426802ffdSnia 			value.un.value.num_channels = info->un.v.num_channels;
14526802ffdSnia 
14626802ffdSnia 		if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
14726802ffdSnia 			err(EXIT_FAILURE, "failed to read mixer control");
14826802ffdSnia 	}
14926802ffdSnia 
15026802ffdSnia 	switch (info->type) {
15126802ffdSnia 	case AUDIO_MIXER_VALUE:
152d257b0e8Snia 		if (info->un.v.delta != 0) {
15326802ffdSnia 			delta = right ? info->un.v.delta : -info->un.v.delta;
154d257b0e8Snia 		} else {
155d257b0e8Snia 			/* delta is 0 in qemu with sb(4) */
156d257b0e8Snia 			delta = right ? 16 : -16;
157d257b0e8Snia 		}
15826802ffdSnia 		/*
15926802ffdSnia 		 * work around strange problem where the level can be
16026802ffdSnia 		 * increased but not decreased, seen with uaudio(4)
16126802ffdSnia 		 */
16226802ffdSnia 		if (delta < 16)
16326802ffdSnia 			delta *= 2;
16426802ffdSnia 		if (aio->channels_unlocked) {
16526802ffdSnia 			level = &value.un.value.level[control->setindex];
16626802ffdSnia 			*level = (unsigned char)adjust_level(*level, delta);
16726802ffdSnia 		} else {
16826802ffdSnia 			for (i = 0; i < value.un.value.num_channels; ++i) {
16926802ffdSnia 				level = &value.un.value.level[i];
17026802ffdSnia 				*level = (unsigned char)adjust_level(*level, delta);
17126802ffdSnia 			}
17226802ffdSnia 		}
17326802ffdSnia 		break;
17426802ffdSnia 	case AUDIO_MIXER_ENUM:
17526802ffdSnia 		for (i = 0; i < info->un.e.num_mem; ++i) {
17626802ffdSnia 			if (info->un.e.member[i].ord == value.un.ord) {
17726802ffdSnia 				cur_index = i;
17826802ffdSnia 				break;
17926802ffdSnia 			}
18026802ffdSnia 		}
18126802ffdSnia 		if (right) {
18226802ffdSnia 			value.un.ord = cur_index < (info->un.e.num_mem - 1) ?
18326802ffdSnia 			    info->un.e.member[cur_index + 1].ord :
18426802ffdSnia 			    info->un.e.member[0].ord;
18526802ffdSnia 		} else {
18626802ffdSnia 			value.un.ord = cur_index > 0 ?
18726802ffdSnia 			    info->un.e.member[cur_index - 1].ord :
18826802ffdSnia 			    info->un.e.member[control->info.un.e.num_mem - 1].ord;
18926802ffdSnia 		}
19026802ffdSnia 		break;
19126802ffdSnia 	case AUDIO_MIXER_SET:
19226802ffdSnia 		if (right) {
19326802ffdSnia 			control->setindex =
19426802ffdSnia 			    control->setindex < (info->un.s.num_mem - 1) ?
19526802ffdSnia 				control->setindex + 1 : 0;
19626802ffdSnia 		} else {
19726802ffdSnia 			control->setindex = control->setindex > 0 ?
19826802ffdSnia 			    control->setindex - 1 :
19926802ffdSnia 				control->info.un.s.num_mem - 1;
20026802ffdSnia 		}
20126802ffdSnia 		break;
20226802ffdSnia 	}
20326802ffdSnia 
20426802ffdSnia 	if (info->type != AUDIO_MIXER_SET) {
20526802ffdSnia 		if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &value) < 0)
20626802ffdSnia 			err(EXIT_FAILURE, "failed to adjust mixer control");
20726802ffdSnia 	}
20826802ffdSnia 
20926802ffdSnia 	draw_control(aio, control, true);
21026802ffdSnia }
21126802ffdSnia 
21226802ffdSnia static int
21326802ffdSnia toggle_set(struct aiomixer *aio)
21426802ffdSnia {
21526802ffdSnia 	struct mixer_ctrl ctrl;
21626802ffdSnia 	struct aiomixer_class *class = &aio->classes[aio->curclass];
21726802ffdSnia 	struct aiomixer_control *control = &class->controls[aio->curcontrol];
21826802ffdSnia 
21926802ffdSnia 	ctrl.dev = control->info.index;
22026802ffdSnia 	ctrl.type = control->info.type;
22126802ffdSnia 
22226802ffdSnia 	if (control->info.type != AUDIO_MIXER_SET)
22326802ffdSnia 		return -1;
22426802ffdSnia 
22526802ffdSnia 	if (ioctl(aio->fd, AUDIO_MIXER_READ, &ctrl) < 0)
22626802ffdSnia 		err(EXIT_FAILURE, "failed to read mixer control");
22726802ffdSnia 
22826802ffdSnia 	ctrl.un.mask ^= control->info.un.s.member[control->setindex].mask;
22926802ffdSnia 
23026802ffdSnia 	if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &ctrl) < 0)
23126802ffdSnia 		err(EXIT_FAILURE, "failed to read mixer control");
23226802ffdSnia 
23326802ffdSnia 	draw_control(aio, control, true);
23426802ffdSnia 	return 0;
23526802ffdSnia }
23626802ffdSnia 
23726802ffdSnia static void
23826802ffdSnia step_up(struct aiomixer *aio)
23926802ffdSnia {
24026802ffdSnia 	struct aiomixer_class *class;
24126802ffdSnia 	struct aiomixer_control *control;
24226802ffdSnia 
24326802ffdSnia 	class = &aio->classes[aio->curclass];
24426802ffdSnia 	control = &class->controls[aio->curcontrol];
24526802ffdSnia 
24626802ffdSnia 	if (aio->channels_unlocked &&
24726802ffdSnia 	    control->info.type == AUDIO_MIXER_VALUE &&
24826802ffdSnia 	    control->setindex > 0) {
24926802ffdSnia 		control->setindex--;
25026802ffdSnia 		draw_control(aio, control, true);
25126802ffdSnia 		return;
25226802ffdSnia 	}
25326802ffdSnia 	select_control(aio, aio->curcontrol - 1);
25426802ffdSnia }
25526802ffdSnia 
25626802ffdSnia static void
25726802ffdSnia step_down(struct aiomixer *aio)
25826802ffdSnia {
25926802ffdSnia 	struct aiomixer_class *class;
26026802ffdSnia 	struct aiomixer_control *control;
26126802ffdSnia 
26226802ffdSnia 	class = &aio->classes[aio->curclass];
26326802ffdSnia 	control = &class->controls[aio->curcontrol];
26426802ffdSnia 
26526802ffdSnia 	if (aio->channels_unlocked &&
26626802ffdSnia 	    control->info.type == AUDIO_MIXER_VALUE &&
26726802ffdSnia 	    control->setindex < (control->info.un.v.num_channels - 1)) {
26826802ffdSnia 		control->setindex++;
26926802ffdSnia 		draw_control(aio, control, true);
27026802ffdSnia 		return;
27126802ffdSnia 	}
27226802ffdSnia 
27326802ffdSnia 	select_control(aio, (aio->curcontrol + 1) % class->numcontrols);
27426802ffdSnia }
27526802ffdSnia 
27626802ffdSnia static int
27726802ffdSnia read_key(struct aiomixer *aio, int ch)
27826802ffdSnia {
27926802ffdSnia 	struct aiomixer_class *class;
28026802ffdSnia 	struct aiomixer_control *control;
28126802ffdSnia 	size_t i;
28226802ffdSnia 
28326802ffdSnia 	switch (ch) {
28426802ffdSnia 	case KEY_RESIZE:
28526802ffdSnia 		class = &aio->classes[aio->curclass];
28626802ffdSnia 		resize_widgets(aio);
28726802ffdSnia 		draw_header(aio);
28826802ffdSnia 		draw_classbar(aio);
28926802ffdSnia 		for (i = 0; i < class->numcontrols; ++i) {
29026802ffdSnia 			draw_control(aio,
29126802ffdSnia 			    &class->controls[i],
29226802ffdSnia 			    aio->state == STATE_CONTROL_SELECT ?
29326802ffdSnia 				(aio->curcontrol == i) : false);
29426802ffdSnia 		}
29526802ffdSnia 		break;
29626802ffdSnia 	case KEY_LEFT:
29726802ffdSnia 	case 'h':
29826802ffdSnia 		if (aio->state == STATE_CLASS_SELECT) {
29926802ffdSnia 			select_class(aio, aio->curclass > 0 ?
30026802ffdSnia 			    aio->curclass - 1 : aio->numclasses - 1);
30126802ffdSnia 		} else if (aio->state == STATE_CONTROL_SELECT) {
30226802ffdSnia 			class = &aio->classes[aio->curclass];
30326802ffdSnia 			slide_control(aio,
30426802ffdSnia 			    &class->controls[aio->curcontrol], false);
30526802ffdSnia 		}
30626802ffdSnia 		break;
30726802ffdSnia 	case KEY_RIGHT:
30826802ffdSnia 	case 'l':
30926802ffdSnia 		if (aio->state == STATE_CLASS_SELECT) {
31026802ffdSnia 			select_class(aio,
31126802ffdSnia 			    (aio->curclass + 1) % aio->numclasses);
31226802ffdSnia 		} else if (aio->state == STATE_CONTROL_SELECT) {
31326802ffdSnia 			class = &aio->classes[aio->curclass];
31426802ffdSnia 			slide_control(aio,
31526802ffdSnia 			    &class->controls[aio->curcontrol], true);
31626802ffdSnia 		}
31726802ffdSnia 		break;
31826802ffdSnia 	case KEY_UP:
31926802ffdSnia 	case 'k':
32026802ffdSnia 		if (aio->state == STATE_CONTROL_SELECT) {
32126802ffdSnia 			if (aio->curcontrol == 0) {
32226802ffdSnia 				class = &aio->classes[aio->curclass];
32326802ffdSnia 				control = &class->controls[aio->curcontrol];
32426802ffdSnia 				control->setindex = -1;
32526802ffdSnia 				aio->state = STATE_CLASS_SELECT;
32626802ffdSnia 				draw_control(aio, control, false);
32726802ffdSnia 			} else {
32826802ffdSnia 				step_up(aio);
32926802ffdSnia 			}
33026802ffdSnia 		}
33126802ffdSnia 		break;
33226802ffdSnia 	case KEY_DOWN:
33326802ffdSnia 	case 'j':
33426802ffdSnia 		if (aio->state == STATE_CLASS_SELECT) {
33526802ffdSnia 			class = &aio->classes[aio->curclass];
33626802ffdSnia 			if (class->numcontrols > 0) {
33726802ffdSnia 				aio->state = STATE_CONTROL_SELECT;
33826802ffdSnia 				select_control(aio, 0);
33926802ffdSnia 			}
34026802ffdSnia 		} else if (aio->state == STATE_CONTROL_SELECT) {
34126802ffdSnia 			step_down(aio);
34226802ffdSnia 		}
34326802ffdSnia 		break;
34426802ffdSnia 	case '\n':
34526802ffdSnia 	case ' ':
34626802ffdSnia 		if (aio->state == STATE_CONTROL_SELECT)
34726802ffdSnia 			toggle_set(aio);
34826802ffdSnia 		break;
34926802ffdSnia 	case '1':
35026802ffdSnia 		select_class(aio, 0);
35126802ffdSnia 		break;
35226802ffdSnia 	case '2':
35326802ffdSnia 		select_class(aio, 1);
35426802ffdSnia 		break;
35526802ffdSnia 	case '3':
35626802ffdSnia 		select_class(aio, 2);
35726802ffdSnia 		break;
35826802ffdSnia 	case '4':
35926802ffdSnia 		select_class(aio, 3);
36026802ffdSnia 		break;
36126802ffdSnia 	case '5':
36226802ffdSnia 		select_class(aio, 4);
36326802ffdSnia 		break;
36426802ffdSnia 	case '6':
36526802ffdSnia 		select_class(aio, 5);
36626802ffdSnia 		break;
36726802ffdSnia 	case '7':
36826802ffdSnia 		select_class(aio, 6);
36926802ffdSnia 		break;
37026802ffdSnia 	case '8':
37126802ffdSnia 		select_class(aio, 7);
37226802ffdSnia 		break;
37326802ffdSnia 	case '9':
37426802ffdSnia 		select_class(aio, 8);
37526802ffdSnia 		break;
37626802ffdSnia 	case 'q':
37726802ffdSnia 	case '\e':
37826802ffdSnia 		if (aio->state == STATE_CONTROL_SELECT) {
37926802ffdSnia 			class = &aio->classes[aio->curclass];
38026802ffdSnia 			control = &class->controls[aio->curcontrol];
38126802ffdSnia 			aio->state = STATE_CLASS_SELECT;
38226802ffdSnia 			draw_control(aio, control, false);
38326802ffdSnia 			break;
38426802ffdSnia 		}
38526802ffdSnia 		return 1;
38626802ffdSnia 	case 'u':
38726802ffdSnia 		aio->channels_unlocked = !aio->channels_unlocked;
38826802ffdSnia 		if (aio->state == STATE_CONTROL_SELECT) {
38926802ffdSnia 			class = &aio->classes[aio->curclass];
39026802ffdSnia 			control = &class->controls[aio->curcontrol];
39126802ffdSnia 			if (control->info.type == AUDIO_MIXER_VALUE)
39226802ffdSnia 				draw_control(aio, control, true);
39326802ffdSnia 		}
39426802ffdSnia 		break;
39526802ffdSnia 	}
39626802ffdSnia 
39726802ffdSnia 	draw_screen(aio);
39826802ffdSnia 	return 0;
39926802ffdSnia }
40026802ffdSnia 
40126802ffdSnia static void
40226802ffdSnia process_device_select(struct aiomixer *aio, unsigned int num_devices)
40326802ffdSnia {
40426802ffdSnia 	unsigned int selected_device = 0;
40526802ffdSnia 	char device_path[16];
40626802ffdSnia 	int ch;
40726802ffdSnia 
40826802ffdSnia 	draw_mixer_select(num_devices, selected_device);
40926802ffdSnia 
41026802ffdSnia 	while ((ch = getch()) != ERR) {
41126802ffdSnia 		switch (ch) {
41226802ffdSnia 		case '\n':
413c4b49835Snia 			clear();
41426802ffdSnia 			(void)snprintf(device_path, sizeof(device_path),
41526802ffdSnia 			    "/dev/mixer%d", selected_device);
41626802ffdSnia 			open_device(aio, device_path);
41726802ffdSnia 			return;
41826802ffdSnia 		case KEY_UP:
41926802ffdSnia 		case 'k':
42026802ffdSnia 			if (selected_device > 0)
42126802ffdSnia 				selected_device--;
42226802ffdSnia 			else
42326802ffdSnia 				selected_device = (num_devices - 1);
42426802ffdSnia 			break;
42526802ffdSnia 		case KEY_DOWN:
42626802ffdSnia 		case 'j':
42726802ffdSnia 			if (selected_device < (num_devices - 1))
42826802ffdSnia 				selected_device++;
42926802ffdSnia 			else
43026802ffdSnia 				selected_device = 0;
43126802ffdSnia 			break;
43226802ffdSnia 		case '1':
43326802ffdSnia 			selected_device = 0;
43426802ffdSnia 			break;
43526802ffdSnia 		case '2':
43626802ffdSnia 			selected_device = 1;
43726802ffdSnia 			break;
43826802ffdSnia 		case '3':
43926802ffdSnia 			selected_device = 2;
44026802ffdSnia 			break;
44126802ffdSnia 		case '4':
44226802ffdSnia 			selected_device = 3;
44326802ffdSnia 			break;
44426802ffdSnia 		case '5':
44526802ffdSnia 			selected_device = 4;
44626802ffdSnia 			break;
44726802ffdSnia 		case '6':
44826802ffdSnia 			selected_device = 5;
44926802ffdSnia 			break;
45026802ffdSnia 		case '7':
45126802ffdSnia 			selected_device = 6;
45226802ffdSnia 			break;
45326802ffdSnia 		case '8':
45426802ffdSnia 			selected_device = 7;
45526802ffdSnia 			break;
45626802ffdSnia 		case '9':
45726802ffdSnia 			selected_device = 8;
45826802ffdSnia 			break;
45926802ffdSnia 		}
46026802ffdSnia 		draw_mixer_select(num_devices, selected_device);
46126802ffdSnia 	}
46226802ffdSnia }
46326802ffdSnia 
46426802ffdSnia static void
46526802ffdSnia open_device(struct aiomixer *aio, const char *device)
46626802ffdSnia {
46726802ffdSnia 	int ch;
46826802ffdSnia 
46926802ffdSnia 	if ((aio->fd = open(device, O_RDWR)) < 0)
47026802ffdSnia 		err(EXIT_FAILURE, "couldn't open mixer device");
47126802ffdSnia 
47226802ffdSnia 	if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0)
47326802ffdSnia 		err(EXIT_FAILURE, "AUDIO_GETDEV failed");
47426802ffdSnia 
47526802ffdSnia 	aio->state = STATE_CLASS_SELECT;
47626802ffdSnia 
47726802ffdSnia 	aiomixer_parse(aio);
47826802ffdSnia 
47926802ffdSnia 	create_widgets(aio);
48026802ffdSnia 
48126802ffdSnia 	draw_header(aio);
48226802ffdSnia 	select_class(aio, 0);
48326802ffdSnia 	draw_screen(aio);
48426802ffdSnia 
48526802ffdSnia 	while ((ch = getch()) != ERR) {
48626802ffdSnia 		if (read_key(aio, ch) != 0)
48726802ffdSnia 			break;
48826802ffdSnia 	}
48926802ffdSnia }
49026802ffdSnia 
491627850c8Schristos static __dead void
49226802ffdSnia on_signal(int dummy)
49326802ffdSnia {
49426802ffdSnia 	endwin();
49526802ffdSnia 	exit(0);
49626802ffdSnia }
49726802ffdSnia 
49826802ffdSnia int
49926802ffdSnia main(int argc, char **argv)
50026802ffdSnia {
50126802ffdSnia 	const char *mixer_device = NULL;
50226802ffdSnia 	struct aiomixer *aio;
50326802ffdSnia 	char mixer_path[32];
50426802ffdSnia 	unsigned int mixer_count = 0;
50526802ffdSnia 	int i, fd;
50626802ffdSnia 	int ch;
5077956bcafSnia 	char *no_color = getenv("NO_COLOR");
50826802ffdSnia 
50926802ffdSnia 	if ((aio = malloc(sizeof(struct aiomixer))) == NULL) {
51026802ffdSnia 		err(EXIT_FAILURE, "malloc failed");
51126802ffdSnia 	}
51226802ffdSnia 
51326802ffdSnia 	while ((ch = getopt(argc, argv, "d:u")) != -1) {
51426802ffdSnia 		switch (ch) {
51526802ffdSnia 		case 'd':
51626802ffdSnia 			mixer_device = optarg;
51726802ffdSnia 			break;
51826802ffdSnia 		case 'u':
51926802ffdSnia 			aio->channels_unlocked = true;
52026802ffdSnia 			break;
52126802ffdSnia 		default:
52226802ffdSnia 			usage();
52326802ffdSnia 			break;
52426802ffdSnia 		}
52526802ffdSnia 	}
52626802ffdSnia 
52726802ffdSnia 	argc -= optind;
52826802ffdSnia 	argv += optind;
52926802ffdSnia 
53026802ffdSnia 	if (initscr() == NULL)
53126802ffdSnia 		err(EXIT_FAILURE, "can't initialize curses");
53226802ffdSnia 
53326802ffdSnia 	(void)signal(SIGHUP, on_signal);
53426802ffdSnia 	(void)signal(SIGINT, on_signal);
53526802ffdSnia 	(void)signal(SIGTERM, on_signal);
53626802ffdSnia 
53726802ffdSnia 	curs_set(0);
53826802ffdSnia 	keypad(stdscr, TRUE);
53926802ffdSnia 	cbreak();
54026802ffdSnia 	noecho();
54126802ffdSnia 
5427956bcafSnia 	aio->use_colour = true;
5437956bcafSnia 
5447956bcafSnia 	if (!has_colors())
5457956bcafSnia 		aio->use_colour = false;
5467956bcafSnia 
5477956bcafSnia 	if (no_color != NULL && no_color[0] != '\0')
5487956bcafSnia 		aio->use_colour = false;
5497956bcafSnia 
5507956bcafSnia 	if (aio->use_colour) {
55126802ffdSnia 		start_color();
5525d5b2a54Snia 		use_default_colors();
55326802ffdSnia 		init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK);
55426802ffdSnia 		init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK);
55526802ffdSnia 		init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN);
55626802ffdSnia 		init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED);
55726802ffdSnia 		init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE);
55826802ffdSnia 		init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW);
55926802ffdSnia 	}
56026802ffdSnia 
56126802ffdSnia 	if (mixer_device != NULL) {
56226802ffdSnia 		open_device(aio, mixer_device);
56326802ffdSnia 	} else {
56426802ffdSnia 		for (i = 0; i < 16; ++i) {
56526802ffdSnia 			(void)snprintf(mixer_path, sizeof(mixer_path),
56626802ffdSnia 			    "/dev/mixer%d", i);
56726802ffdSnia 			fd = open(mixer_path, O_RDWR);
56826802ffdSnia 			if (fd == -1)
56926802ffdSnia 				break;
57026802ffdSnia 			close(fd);
57126802ffdSnia 			mixer_count++;
57226802ffdSnia 		}
57326802ffdSnia 
57426802ffdSnia 		if (mixer_count > 1) {
57526802ffdSnia 			process_device_select(aio, mixer_count);
57626802ffdSnia 		} else {
57726802ffdSnia 			open_device(aio, _PATH_MIXER);
57826802ffdSnia 		}
57926802ffdSnia 	}
58026802ffdSnia 
58126802ffdSnia 	endwin();
58226802ffdSnia 	close(aio->fd);
58326802ffdSnia 	free(aio);
58426802ffdSnia 
58526802ffdSnia 	return 0;
58626802ffdSnia }
587