Home | History | Annotate | Line # | Download | only in aiomixer
draw.c revision 1.1
      1 /* $NetBSD: draw.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
      2 /*-
      3  * Copyright (c) 2021 The NetBSD Foundation, Inc.
      4  * All rights reserved.
      5  *
      6  * This code is derived from software contributed to The NetBSD Foundation
      7  * by Nia Alarie.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted provided that the following conditions
     11  * are met:
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in the
     16  *    documentation and/or other materials provided with the distribution.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     20  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     22  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     28  * POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 #include <sys/audioio.h>
     31 #include <sys/ioctl.h>
     32 #include <fcntl.h>
     33 #include <unistd.h>
     34 #include <curses.h>
     35 #include <err.h>
     36 #include <stdlib.h>
     37 #include "draw.h"
     38 
     39 static void bold_on(WINDOW *);
     40 static void bold_off(WINDOW *);
     41 static int get_enum_color(const char *);
     42 static void draw_enum(struct aiomixer_control *, int, bool);
     43 static void draw_set(struct aiomixer_control *, int);
     44 static void draw_levels(struct aiomixer_control *,
     45     const struct mixer_level *, bool, bool);
     46 
     47 static void
     48 bold_on(WINDOW *w)
     49 {
     50 	/*
     51 	 * Some (XXX: which?) legacy terminals do not support a Bold
     52 	 * attribute.  In this case, we fall back to standout.
     53 	 */
     54 	if (termattrs() & A_BOLD)
     55 		wattron(w, A_BOLD);
     56 	else
     57 		wattron(w, A_STANDOUT);
     58 }
     59 
     60 static void
     61 bold_off(WINDOW *w)
     62 {
     63 	chtype attrs = getattrs(w);
     64 
     65 	if (attrs & A_BOLD)
     66 		wattroff(w, A_BOLD);
     67 	if (attrs & A_STANDOUT)
     68 		wattroff(w, A_STANDOUT);
     69 }
     70 
     71 void
     72 draw_mixer_select(unsigned int num_mixers, unsigned int selected_mixer)
     73 {
     74 	struct audio_device dev;
     75 	char mixer_path[16];
     76 	unsigned int i;
     77 	int fd;
     78 
     79 	mvprintw(0, 0, "Select a mixer device:\n");
     80 
     81 	for (i = 0; i < num_mixers; ++i) {
     82 		(void)snprintf(mixer_path, sizeof(mixer_path),
     83 		    "/dev/mixer%d", i);
     84 		fd = open(mixer_path, O_RDWR);
     85 		if (fd == -1)
     86 			break;
     87 		if (ioctl(fd, AUDIO_GETDEV, &dev) < 0) {
     88 			close(fd);
     89 			break;
     90 		}
     91 		close(fd);
     92 		if (selected_mixer == i) {
     93 			bold_on(stdscr);
     94 			addstr("[*] ");
     95 		} else {
     96 			addstr("[ ] ");
     97 		}
     98 		printw("%s: %s %s %s\n", mixer_path,
     99 		    dev.name, dev.version, dev.config);
    100 		if (selected_mixer == i)
    101 			bold_off(stdscr);
    102 	}
    103 }
    104 
    105 void
    106 draw_control(struct aiomixer *aio,
    107     struct aiomixer_control *control, bool selected)
    108 {
    109 	struct mixer_ctrl value;
    110 
    111 	value.dev = control->info.index;
    112 	value.type = control->info.type;
    113 	if (value.type == AUDIO_MIXER_VALUE)
    114 		value.un.value.num_channels = control->info.un.v.num_channels;
    115 
    116 	if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
    117 		err(EXIT_FAILURE, "failed to read from mixer device");
    118 
    119 	wclear(control->widgetpad);
    120 	if (selected) {
    121 		bold_on(control->widgetpad);
    122 		if (has_colors()) {
    123 			wattron(control->widgetpad,
    124 			    COLOR_PAIR(COLOR_CONTROL_SELECTED));
    125 		}
    126 		waddch(control->widgetpad, '*');
    127 		if (has_colors()) {
    128 			wattroff(control->widgetpad,
    129 			    COLOR_PAIR(COLOR_CONTROL_SELECTED));
    130 		}
    131 	}
    132 	wprintw(control->widgetpad, "%s\n", control->info.label.name);
    133 	if (selected)
    134 		bold_off(control->widgetpad);
    135 
    136 	switch (value.type) {
    137 	case AUDIO_MIXER_ENUM:
    138 		draw_enum(control, value.un.ord, selected);
    139 		break;
    140 	case AUDIO_MIXER_SET:
    141 		draw_set(control, value.un.mask);
    142 		break;
    143 	case AUDIO_MIXER_VALUE:
    144 		draw_levels(control, &value.un.value,
    145 		    aio->channels_unlocked, selected);
    146 		break;
    147 	}
    148 }
    149 
    150 void
    151 draw_screen(struct aiomixer *aio)
    152 {
    153 	int i, max_y;
    154 
    155 	/* Clear any leftovers if the screen changed. */
    156 	if (aio->widgets_resized) {
    157 		aio->widgets_resized = false;
    158 		max_y = getmaxy(stdscr);
    159 		for (i = 3; i < max_y; ++i) {
    160 			mvprintw(i, 0, "\n");
    161 		}
    162 	}
    163 
    164 	wnoutrefresh(stdscr);
    165 	wnoutrefresh(aio->header);
    166 	wnoutrefresh(aio->classbar);
    167 	pnoutrefresh(aio->classes[aio->curclass].widgetpad,
    168 	    aio->class_scroll_y, 0,
    169 	    3, 0,
    170 	    1 + aio->classes[aio->curclass].height, getmaxx(stdscr));
    171 	doupdate();
    172 }
    173 
    174 static int
    175 get_enum_color(const char *name)
    176 {
    177 	if (strcmp(name, AudioNon) == 0) {
    178 		return COLOR_ENUM_ON;
    179 	}
    180 	if (strcmp(name, AudioNoff) == 0) {
    181 		return COLOR_ENUM_OFF;
    182 	}
    183 
    184 	return COLOR_ENUM_MISC;
    185 }
    186 
    187 static void
    188 draw_enum(struct aiomixer_control *control, int ord, bool selected)
    189 {
    190 	struct audio_mixer_enum *e;
    191 	int color = COLOR_ENUM_MISC;
    192 	int i;
    193 
    194 	for (i = 0; i < control->info.un.e.num_mem; ++i) {
    195 		e = &control->info.un.e;
    196 		if (ord == e->member[i].ord && selected)
    197 			bold_on(control->widgetpad);
    198 		waddch(control->widgetpad, '[');
    199 		if (ord == e->member[i].ord) {
    200 			if (has_colors()) {
    201 				color = get_enum_color(e->member[i].label.name);
    202 				wattron(control->widgetpad,
    203 					COLOR_PAIR(color));
    204 			} else {
    205 				waddch(control->widgetpad, '*');
    206 			}
    207 		}
    208 		wprintw(control->widgetpad, "%s", e->member[i].label.name);
    209 		if (ord == control->info.un.e.member[i].ord) {
    210 			if (has_colors()) {
    211 				wattroff(control->widgetpad,
    212 					COLOR_PAIR(color));
    213 			}
    214 		}
    215 		waddch(control->widgetpad, ']');
    216 		if (ord == e->member[i].ord && selected)
    217 			bold_off(control->widgetpad);
    218 		if (i != (e->num_mem - 1))
    219 			waddstr(control->widgetpad, ", ");
    220 	}
    221 	waddch(control->widgetpad, '\n');
    222 }
    223 
    224 static void
    225 draw_set(struct aiomixer_control *control, int mask)
    226 {
    227 	int i;
    228 
    229 	for (i = 0; i < control->info.un.s.num_mem; ++i) {
    230 		waddch(control->widgetpad, '[');
    231 		if (mask & control->info.un.s.member[i].mask) {
    232 			if (has_colors()) {
    233 				wattron(control->widgetpad,
    234 					COLOR_PAIR(COLOR_SET_SELECTED));
    235 			}
    236 			waddch(control->widgetpad, '*');
    237 			if (has_colors()) {
    238 				wattroff(control->widgetpad,
    239 					COLOR_PAIR(COLOR_SET_SELECTED));
    240 			}
    241 		} else {
    242 			waddch(control->widgetpad, ' ');
    243 		}
    244 		waddstr(control->widgetpad, "] ");
    245 		if (control->setindex == i) {
    246 			bold_on(control->widgetpad);
    247 			waddch(control->widgetpad, '*');
    248 		}
    249 		wprintw(control->widgetpad, "%s",
    250 		    control->info.un.s.member[i].label.name);
    251 		if (control->setindex == i)
    252 			bold_off(control->widgetpad);
    253 		if (i != (control->info.un.s.num_mem - 1))
    254 			waddstr(control->widgetpad, ", ");
    255 	}
    256 }
    257 
    258 static void
    259 draw_levels(struct aiomixer_control *control,
    260     const struct mixer_level *levels, bool channels_unlocked, bool selected)
    261 {
    262 	int i;
    263 	int j, nchars;
    264 
    265 	for (i = 0; i < control->info.un.v.num_channels; ++i) {
    266 		if ((selected && !channels_unlocked) ||
    267 		    (control->setindex == i && channels_unlocked)) {
    268 			bold_on(control->widgetpad);
    269 		}
    270 		wprintw(control->widgetpad, "[%3u/%3u ",
    271 		    levels->level[i], AUDIO_MAX_GAIN);
    272 		if (has_colors()) {
    273 			wattron(control->widgetpad,
    274 				COLOR_PAIR(COLOR_LEVELS));
    275 		}
    276 		nchars = (levels->level[i] *
    277 		    (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN;
    278 		for (j = 0; j < nchars; ++j)
    279 			waddch(control->widgetpad, '*');
    280 		if (has_colors()) {
    281 			wattroff(control->widgetpad,
    282 				COLOR_PAIR(COLOR_LEVELS));
    283 		}
    284 		nchars = getmaxx(control->widgetpad) - 11 - nchars;
    285 		for (j = 0; j < nchars; ++j)
    286 			waddch(control->widgetpad, ' ');
    287 		wprintw(control->widgetpad, "]\n");
    288 		if ((selected && !channels_unlocked) ||
    289 		    (control->setindex == i && channels_unlocked)) {
    290 			bold_off(control->widgetpad);
    291 		}
    292 	}
    293 }
    294 
    295 void
    296 draw_classbar(struct aiomixer *aio)
    297 {
    298 	unsigned int i;
    299 
    300 	wmove(aio->classbar, 0, 0);
    301 
    302 	for (i = 0; i < aio->numclasses; ++i) {
    303 		if (aio->curclass == i)
    304 			bold_on(aio->classbar);
    305 		wprintw(aio->classbar, "[%u:", i + 1);
    306 		if (aio->curclass == i) {
    307 			if (has_colors()) {
    308 				wattron(aio->classbar,
    309 					COLOR_PAIR(COLOR_CONTROL_SELECTED));
    310 			}
    311 			waddch(aio->classbar, '*');
    312 			if (has_colors()) {
    313 				wattroff(aio->classbar,
    314 					COLOR_PAIR(COLOR_CONTROL_SELECTED));
    315 			}
    316 		}
    317 		waddstr(aio->classbar, aio->classes[i].name);
    318 		if (aio->curclass == i)
    319 			bold_off(aio->classbar);
    320 		waddstr(aio->classbar, "] ");
    321 	}
    322 
    323 	wprintw(aio->classbar, "\n\n");
    324 }
    325 
    326 void
    327 draw_header(struct aiomixer *aio)
    328 {
    329 	mvwaddstr(aio->header, 0,
    330 	    getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer") + 1,
    331 	    "NetBSD audio mixer");
    332 
    333 	if (aio->mixerdev.version[0] != '\0') {
    334 		wprintw(aio->header, "%s %s",
    335 		    aio->mixerdev.name, aio->mixerdev.version);
    336 	} else {
    337 		wprintw(aio->header, "%s", aio->mixerdev.name);
    338 	}
    339 }
    340 
    341 void
    342 create_widgets(struct aiomixer *aio)
    343 {
    344 	size_t i, j;
    345 	struct aiomixer_class *class;
    346 	struct aiomixer_control *control;
    347 
    348 	aio->header = newwin(1, getmaxx(stdscr), 0, 0);
    349 	if (aio->header == NULL)
    350 		errx(EXIT_FAILURE, "failed to create window");
    351 
    352 	aio->classbar = newwin(2, getmaxx(stdscr), 1, 0);
    353 	if (aio->classbar == NULL)
    354 		errx(EXIT_FAILURE, "failed to create window");
    355 
    356 	for (i = 0; i < aio->numclasses; ++i) {
    357 		class = &aio->classes[i];
    358 		class->height = 0;
    359 		class->widgetpad = newpad(4 * __arraycount(class->controls),
    360 		    getmaxx(stdscr));
    361 		if (class->widgetpad == NULL)
    362 			errx(EXIT_FAILURE, "failed to create curses pad");
    363 		for (j = 0; j < class->numcontrols; ++j) {
    364 			control = &class->controls[j];
    365 			switch (control->info.type) {
    366 			case AUDIO_MIXER_VALUE:
    367 				control->height = 2 +
    368 				    control->info.un.v.num_channels;
    369 				break;
    370 			case AUDIO_MIXER_ENUM:
    371 			case AUDIO_MIXER_SET:
    372 				control->height = 3;
    373 				break;
    374 			}
    375 			control->widgetpad = subpad(class->widgetpad,
    376 			    control->height, getmaxx(stdscr),
    377 			    class->height, 0);
    378 			if (control->widgetpad == NULL)
    379 				errx(EXIT_FAILURE, "failed to create curses pad");
    380 			control->widget_y = class->height;
    381 			class->height += control->height;
    382 		}
    383 		wresize(class->widgetpad, class->height, getmaxx(stdscr));
    384 	}
    385 
    386 	aio->last_max_x = getmaxx(stdscr);
    387 }
    388 
    389 void
    390 resize_widgets(struct aiomixer *aio)
    391 {
    392 	size_t i, j;
    393 	struct aiomixer_class *class;
    394 	struct aiomixer_control *control;
    395 	int max_x;
    396 
    397 	max_x = getmaxx(stdscr);
    398 
    399 	if (aio->last_max_x != max_x) {
    400 		aio->last_max_x = max_x;
    401 		wresize(aio->header, 1, max_x);
    402 		wresize(aio->classbar, 2, max_x);
    403 
    404 		for (i = 0; i < aio->numclasses; ++i) {
    405 			class = &aio->classes[i];
    406 			wresize(class->widgetpad, class->height, max_x);
    407 			for (j = 0; j < class->numcontrols; ++j) {
    408 				control = &class->controls[j];
    409 				wresize(control->widgetpad,
    410 				    control->height, max_x);
    411 			}
    412 		}
    413 	}
    414 
    415 	aio->widgets_resized = true;
    416 }
    417