1*7956bcafSnia /* $NetBSD: draw.c,v 1.10 2023/06/29 19:06:54 nia 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 <curses.h>
3526802ffdSnia #include <err.h>
3626802ffdSnia #include <stdlib.h>
3726802ffdSnia #include "draw.h"
3826802ffdSnia
3926802ffdSnia static int get_enum_color(const char *);
40*7956bcafSnia static void draw_enum(struct aiomixer_control *, int, bool, bool);
41*7956bcafSnia static void draw_set(struct aiomixer_control *, int, bool);
4226802ffdSnia static void draw_levels(struct aiomixer_control *,
43*7956bcafSnia const struct mixer_level *, bool, bool, bool);
4426802ffdSnia
4526802ffdSnia void
draw_mixer_select(unsigned int num_mixers,unsigned int selected_mixer)4626802ffdSnia draw_mixer_select(unsigned int num_mixers, unsigned int selected_mixer)
4726802ffdSnia {
4826802ffdSnia struct audio_device dev;
4926802ffdSnia char mixer_path[16];
5026802ffdSnia unsigned int i;
5126802ffdSnia int fd;
5226802ffdSnia
5326802ffdSnia mvprintw(0, 0, "Select a mixer device:\n");
5426802ffdSnia
5526802ffdSnia for (i = 0; i < num_mixers; ++i) {
5626802ffdSnia (void)snprintf(mixer_path, sizeof(mixer_path),
5726802ffdSnia "/dev/mixer%d", i);
5826802ffdSnia fd = open(mixer_path, O_RDWR);
5926802ffdSnia if (fd == -1)
6026802ffdSnia break;
6126802ffdSnia if (ioctl(fd, AUDIO_GETDEV, &dev) < 0) {
6226802ffdSnia close(fd);
6326802ffdSnia break;
6426802ffdSnia }
6526802ffdSnia close(fd);
6626802ffdSnia if (selected_mixer == i) {
675249aad0Snia attron(A_STANDOUT);
6826802ffdSnia addstr("[*] ");
6926802ffdSnia } else {
7026802ffdSnia addstr("[ ] ");
7126802ffdSnia }
7226802ffdSnia printw("%s: %s %s %s\n", mixer_path,
7326802ffdSnia dev.name, dev.version, dev.config);
7426802ffdSnia if (selected_mixer == i)
755249aad0Snia attroff(A_STANDOUT);
7626802ffdSnia }
7726802ffdSnia }
7826802ffdSnia
7926802ffdSnia void
draw_control(struct aiomixer * aio,struct aiomixer_control * control,bool selected)8026802ffdSnia draw_control(struct aiomixer *aio,
8126802ffdSnia struct aiomixer_control *control, bool selected)
8226802ffdSnia {
8326802ffdSnia struct mixer_ctrl value;
8426802ffdSnia
8526802ffdSnia value.dev = control->info.index;
8626802ffdSnia value.type = control->info.type;
8726802ffdSnia if (value.type == AUDIO_MIXER_VALUE)
8826802ffdSnia value.un.value.num_channels = control->info.un.v.num_channels;
8926802ffdSnia
9026802ffdSnia if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
9126802ffdSnia err(EXIT_FAILURE, "failed to read from mixer device");
9226802ffdSnia
9326802ffdSnia wclear(control->widgetpad);
945249aad0Snia if (selected)
955249aad0Snia wattron(control->widgetpad, A_STANDOUT);
96e2c130f7Snia if (value.type == AUDIO_MIXER_VALUE)
97e2c130f7Snia wprintw(control->widgetpad, "%s (%s)\n",
98e2c130f7Snia control->info.label.name, control->info.un.v.units.name);
99e2c130f7Snia else
10026802ffdSnia wprintw(control->widgetpad, "%s\n", control->info.label.name);
10126802ffdSnia if (selected)
1025249aad0Snia wattroff(control->widgetpad, A_STANDOUT);
10326802ffdSnia
10426802ffdSnia switch (value.type) {
10526802ffdSnia case AUDIO_MIXER_ENUM:
106*7956bcafSnia draw_enum(control, value.un.ord, selected, aio->use_colour);
10726802ffdSnia break;
10826802ffdSnia case AUDIO_MIXER_SET:
109*7956bcafSnia draw_set(control, value.un.mask, aio->use_colour);
11026802ffdSnia break;
11126802ffdSnia case AUDIO_MIXER_VALUE:
11226802ffdSnia draw_levels(control, &value.un.value,
113*7956bcafSnia aio->channels_unlocked, selected, aio->use_colour);
11426802ffdSnia break;
11526802ffdSnia }
116a7559334Snia
117a7559334Snia wprintw(control->widgetpad, "\n");
11826802ffdSnia }
11926802ffdSnia
12026802ffdSnia void
draw_screen(struct aiomixer * aio)12126802ffdSnia draw_screen(struct aiomixer *aio)
12226802ffdSnia {
12326802ffdSnia int i, max_y;
12426802ffdSnia
12526802ffdSnia /* Clear any leftovers if the screen changed. */
12626802ffdSnia if (aio->widgets_resized) {
12726802ffdSnia aio->widgets_resized = false;
12826802ffdSnia max_y = getmaxy(stdscr);
12926802ffdSnia for (i = 3; i < max_y; ++i) {
13026802ffdSnia mvprintw(i, 0, "\n");
13126802ffdSnia }
13226802ffdSnia }
13326802ffdSnia
13426802ffdSnia wnoutrefresh(stdscr);
13526802ffdSnia wnoutrefresh(aio->header);
13626802ffdSnia wnoutrefresh(aio->classbar);
137bd36d9a7Snia max_y = aio->classes[aio->curclass].height + 1;
138bd36d9a7Snia max_y -= aio->class_scroll_y;
139bd36d9a7Snia if (max_y > (getmaxy(stdscr) - 3))
140bd36d9a7Snia max_y = getmaxy(stdscr) - 3;
14126802ffdSnia pnoutrefresh(aio->classes[aio->curclass].widgetpad,
14226802ffdSnia aio->class_scroll_y, 0,
14326802ffdSnia 3, 0,
144bd36d9a7Snia max_y, getmaxx(stdscr));
14526802ffdSnia doupdate();
14626802ffdSnia }
14726802ffdSnia
14826802ffdSnia static int
get_enum_color(const char * name)14926802ffdSnia get_enum_color(const char *name)
15026802ffdSnia {
15126802ffdSnia if (strcmp(name, AudioNon) == 0) {
15226802ffdSnia return COLOR_ENUM_ON;
15326802ffdSnia }
15426802ffdSnia if (strcmp(name, AudioNoff) == 0) {
15526802ffdSnia return COLOR_ENUM_OFF;
15626802ffdSnia }
15726802ffdSnia
15826802ffdSnia return COLOR_ENUM_MISC;
15926802ffdSnia }
16026802ffdSnia
16126802ffdSnia static void
draw_enum(struct aiomixer_control * control,int ord,bool selected,bool colour)162*7956bcafSnia draw_enum(struct aiomixer_control *control, int ord,
163*7956bcafSnia bool selected, bool colour)
16426802ffdSnia {
16526802ffdSnia struct audio_mixer_enum *e;
16626802ffdSnia int color = COLOR_ENUM_MISC;
16726802ffdSnia int i;
16826802ffdSnia
16926802ffdSnia for (i = 0; i < control->info.un.e.num_mem; ++i) {
17026802ffdSnia e = &control->info.un.e;
1715249aad0Snia if (ord == e->member[i].ord && selected) {
1725249aad0Snia if (termattrs() & A_BOLD)
1735249aad0Snia wattron(control->widgetpad, A_BOLD);
1745249aad0Snia else
1755249aad0Snia wattron(control->widgetpad, A_STANDOUT);
1765249aad0Snia }
17726802ffdSnia waddch(control->widgetpad, '[');
17826802ffdSnia if (ord == e->member[i].ord) {
179*7956bcafSnia if (colour) {
18026802ffdSnia color = get_enum_color(e->member[i].label.name);
18126802ffdSnia wattron(control->widgetpad,
18226802ffdSnia COLOR_PAIR(color));
18326802ffdSnia } else {
18426802ffdSnia waddch(control->widgetpad, '*');
18526802ffdSnia }
18626802ffdSnia }
18726802ffdSnia wprintw(control->widgetpad, "%s", e->member[i].label.name);
18826802ffdSnia if (ord == control->info.un.e.member[i].ord) {
189*7956bcafSnia if (colour) {
19026802ffdSnia wattroff(control->widgetpad,
19126802ffdSnia COLOR_PAIR(color));
19226802ffdSnia }
19326802ffdSnia }
19426802ffdSnia waddch(control->widgetpad, ']');
1955249aad0Snia if (ord == e->member[i].ord && selected) {
1965249aad0Snia if (termattrs() & A_BOLD)
1975249aad0Snia wattroff(control->widgetpad, A_BOLD);
1985249aad0Snia else
1995249aad0Snia wattroff(control->widgetpad, A_STANDOUT);
2005249aad0Snia }
20126802ffdSnia if (i != (e->num_mem - 1))
20226802ffdSnia waddstr(control->widgetpad, ", ");
20326802ffdSnia }
20426802ffdSnia waddch(control->widgetpad, '\n');
20526802ffdSnia }
20626802ffdSnia
20726802ffdSnia static void
draw_set(struct aiomixer_control * control,int mask,bool colour)208*7956bcafSnia draw_set(struct aiomixer_control *control, int mask, bool colour)
20926802ffdSnia {
21026802ffdSnia int i;
21126802ffdSnia
21226802ffdSnia for (i = 0; i < control->info.un.s.num_mem; ++i) {
21326802ffdSnia waddch(control->widgetpad, '[');
21426802ffdSnia if (mask & control->info.un.s.member[i].mask) {
215*7956bcafSnia if (colour) {
21626802ffdSnia wattron(control->widgetpad,
21726802ffdSnia COLOR_PAIR(COLOR_SET_SELECTED));
21826802ffdSnia }
21926802ffdSnia waddch(control->widgetpad, '*');
220*7956bcafSnia if (colour) {
22126802ffdSnia wattroff(control->widgetpad,
22226802ffdSnia COLOR_PAIR(COLOR_SET_SELECTED));
22326802ffdSnia }
22426802ffdSnia } else {
22526802ffdSnia waddch(control->widgetpad, ' ');
22626802ffdSnia }
22726802ffdSnia waddstr(control->widgetpad, "] ");
22826802ffdSnia if (control->setindex == i) {
2295249aad0Snia if (termattrs() & A_BOLD)
2305249aad0Snia wattron(control->widgetpad, A_BOLD);
2315249aad0Snia else
2325249aad0Snia wattron(control->widgetpad, A_STANDOUT);
23326802ffdSnia }
23426802ffdSnia wprintw(control->widgetpad, "%s",
23526802ffdSnia control->info.un.s.member[i].label.name);
2365249aad0Snia if (control->setindex == i) {
2375249aad0Snia if (termattrs() & A_BOLD)
2385249aad0Snia wattroff(control->widgetpad, A_BOLD);
2395249aad0Snia else
2405249aad0Snia wattroff(control->widgetpad, A_STANDOUT);
2415249aad0Snia }
24226802ffdSnia if (i != (control->info.un.s.num_mem - 1))
24326802ffdSnia waddstr(control->widgetpad, ", ");
24426802ffdSnia }
24526802ffdSnia }
24626802ffdSnia
24726802ffdSnia static void
draw_levels(struct aiomixer_control * control,const struct mixer_level * levels,bool channels_unlocked,bool selected,bool colour)24826802ffdSnia draw_levels(struct aiomixer_control *control,
249*7956bcafSnia const struct mixer_level *levels, bool channels_unlocked,
250*7956bcafSnia bool selected, bool colour)
25126802ffdSnia {
25226802ffdSnia int i;
25326802ffdSnia int j, nchars;
25426802ffdSnia
25526802ffdSnia for (i = 0; i < control->info.un.v.num_channels; ++i) {
25626802ffdSnia if ((selected && !channels_unlocked) ||
25726802ffdSnia (control->setindex == i && channels_unlocked)) {
2585249aad0Snia if (termattrs() & A_BOLD)
2595249aad0Snia wattron(control->widgetpad, A_BOLD);
2605249aad0Snia else
2615249aad0Snia wattron(control->widgetpad, A_STANDOUT);
26226802ffdSnia }
26326802ffdSnia wprintw(control->widgetpad, "[%3u/%3u ",
26426802ffdSnia levels->level[i], AUDIO_MAX_GAIN);
265*7956bcafSnia if (colour) {
26626802ffdSnia wattron(control->widgetpad,
26726802ffdSnia COLOR_PAIR(COLOR_LEVELS));
26826802ffdSnia }
26926802ffdSnia nchars = (levels->level[i] *
27026802ffdSnia (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN;
27126802ffdSnia for (j = 0; j < nchars; ++j)
27226802ffdSnia waddch(control->widgetpad, '*');
273*7956bcafSnia if (colour) {
27426802ffdSnia wattroff(control->widgetpad,
27526802ffdSnia COLOR_PAIR(COLOR_LEVELS));
27626802ffdSnia }
27726802ffdSnia nchars = getmaxx(control->widgetpad) - 11 - nchars;
27826802ffdSnia for (j = 0; j < nchars; ++j)
27926802ffdSnia waddch(control->widgetpad, ' ');
28026802ffdSnia wprintw(control->widgetpad, "]\n");
28126802ffdSnia if ((selected && !channels_unlocked) ||
28226802ffdSnia (control->setindex == i && channels_unlocked)) {
2835249aad0Snia if (termattrs() & A_BOLD)
2845249aad0Snia wattroff(control->widgetpad, A_BOLD);
2855249aad0Snia else
2865249aad0Snia wattroff(control->widgetpad, A_STANDOUT);
28726802ffdSnia }
28826802ffdSnia }
28926802ffdSnia }
29026802ffdSnia
29126802ffdSnia void
draw_classbar(struct aiomixer * aio)29226802ffdSnia draw_classbar(struct aiomixer *aio)
29326802ffdSnia {
29426802ffdSnia unsigned int i;
29526802ffdSnia
29626802ffdSnia wmove(aio->classbar, 0, 0);
29726802ffdSnia
29826802ffdSnia for (i = 0; i < aio->numclasses; ++i) {
29926802ffdSnia if (aio->curclass == i)
3005249aad0Snia wattron(aio->classbar, A_STANDOUT);
3015249aad0Snia wprintw(aio->classbar, "[%u:%s]",
3025249aad0Snia i + 1, aio->classes[i].name);
30326802ffdSnia if (aio->curclass == i)
3045249aad0Snia wattroff(aio->classbar, A_STANDOUT);
3055249aad0Snia waddch(aio->classbar, ' ');
30626802ffdSnia }
30726802ffdSnia
30826802ffdSnia wprintw(aio->classbar, "\n\n");
30926802ffdSnia }
31026802ffdSnia
31126802ffdSnia void
draw_header(struct aiomixer * aio)31226802ffdSnia draw_header(struct aiomixer *aio)
31326802ffdSnia {
3145249aad0Snia wprintw(aio->header, "\n");
31526802ffdSnia mvwaddstr(aio->header, 0,
316e81c68c7Snia getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer"),
31726802ffdSnia "NetBSD audio mixer");
31826802ffdSnia
31926802ffdSnia if (aio->mixerdev.version[0] != '\0') {
3205249aad0Snia mvwprintw(aio->header, 0, 0, "%s %s",
32126802ffdSnia aio->mixerdev.name, aio->mixerdev.version);
32226802ffdSnia } else {
3235249aad0Snia mvwprintw(aio->header, 0, 0, "%s", aio->mixerdev.name);
32426802ffdSnia }
32526802ffdSnia }
32626802ffdSnia
32726802ffdSnia void
create_widgets(struct aiomixer * aio)32826802ffdSnia create_widgets(struct aiomixer *aio)
32926802ffdSnia {
33026802ffdSnia size_t i, j;
33126802ffdSnia struct aiomixer_class *class;
33226802ffdSnia struct aiomixer_control *control;
33326802ffdSnia
33426802ffdSnia aio->header = newwin(1, getmaxx(stdscr), 0, 0);
33526802ffdSnia if (aio->header == NULL)
33626802ffdSnia errx(EXIT_FAILURE, "failed to create window");
33726802ffdSnia
33826802ffdSnia aio->classbar = newwin(2, getmaxx(stdscr), 1, 0);
33926802ffdSnia if (aio->classbar == NULL)
34026802ffdSnia errx(EXIT_FAILURE, "failed to create window");
34126802ffdSnia
34226802ffdSnia for (i = 0; i < aio->numclasses; ++i) {
34326802ffdSnia class = &aio->classes[i];
34426802ffdSnia class->height = 0;
34526802ffdSnia class->widgetpad = newpad(4 * __arraycount(class->controls),
34626802ffdSnia getmaxx(stdscr));
34726802ffdSnia if (class->widgetpad == NULL)
34826802ffdSnia errx(EXIT_FAILURE, "failed to create curses pad");
34926802ffdSnia for (j = 0; j < class->numcontrols; ++j) {
35026802ffdSnia control = &class->controls[j];
35126802ffdSnia switch (control->info.type) {
35226802ffdSnia case AUDIO_MIXER_VALUE:
35326802ffdSnia control->height = 2 +
35426802ffdSnia control->info.un.v.num_channels;
35526802ffdSnia break;
35626802ffdSnia case AUDIO_MIXER_ENUM:
35726802ffdSnia case AUDIO_MIXER_SET:
35826802ffdSnia control->height = 3;
35926802ffdSnia break;
36026802ffdSnia }
36126802ffdSnia control->widgetpad = subpad(class->widgetpad,
36226802ffdSnia control->height, getmaxx(stdscr),
36326802ffdSnia class->height, 0);
36426802ffdSnia if (control->widgetpad == NULL)
36526802ffdSnia errx(EXIT_FAILURE, "failed to create curses pad");
36626802ffdSnia control->widget_y = class->height;
36726802ffdSnia class->height += control->height;
36826802ffdSnia }
36926802ffdSnia wresize(class->widgetpad, class->height, getmaxx(stdscr));
37026802ffdSnia }
37126802ffdSnia
37226802ffdSnia aio->last_max_x = getmaxx(stdscr);
37326802ffdSnia }
37426802ffdSnia
37526802ffdSnia void
resize_widgets(struct aiomixer * aio)37626802ffdSnia resize_widgets(struct aiomixer *aio)
37726802ffdSnia {
37826802ffdSnia size_t i, j;
37926802ffdSnia struct aiomixer_class *class;
38026802ffdSnia struct aiomixer_control *control;
38126802ffdSnia int max_x;
38226802ffdSnia
38326802ffdSnia max_x = getmaxx(stdscr);
38426802ffdSnia
38526802ffdSnia if (aio->last_max_x != max_x) {
38626802ffdSnia aio->last_max_x = max_x;
38726802ffdSnia wresize(aio->header, 1, max_x);
38826802ffdSnia wresize(aio->classbar, 2, max_x);
38926802ffdSnia
39026802ffdSnia for (i = 0; i < aio->numclasses; ++i) {
39126802ffdSnia class = &aio->classes[i];
39226802ffdSnia wresize(class->widgetpad, class->height, max_x);
39326802ffdSnia for (j = 0; j < class->numcontrols; ++j) {
39426802ffdSnia control = &class->controls[j];
39526802ffdSnia wresize(control->widgetpad,
39626802ffdSnia control->height, max_x);
39726802ffdSnia }
39826802ffdSnia }
39926802ffdSnia }
40026802ffdSnia
40126802ffdSnia aio->widgets_resized = true;
40226802ffdSnia }
403