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