1 /* $NetBSD: draw.c,v 1.7 2021/05/29 09:11:41 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 int get_enum_color(const char *); 40 static void draw_enum(struct aiomixer_control *, int, bool); 41 static void draw_set(struct aiomixer_control *, int); 42 static void draw_levels(struct aiomixer_control *, 43 const struct mixer_level *, bool, bool); 44 45 void 46 draw_mixer_select(unsigned int num_mixers, unsigned int selected_mixer) 47 { 48 struct audio_device dev; 49 char mixer_path[16]; 50 unsigned int i; 51 int fd; 52 53 mvprintw(0, 0, "Select a mixer device:\n"); 54 55 for (i = 0; i < num_mixers; ++i) { 56 (void)snprintf(mixer_path, sizeof(mixer_path), 57 "/dev/mixer%d", i); 58 fd = open(mixer_path, O_RDWR); 59 if (fd == -1) 60 break; 61 if (ioctl(fd, AUDIO_GETDEV, &dev) < 0) { 62 close(fd); 63 break; 64 } 65 close(fd); 66 if (selected_mixer == i) { 67 attron(A_STANDOUT); 68 addstr("[*] "); 69 } else { 70 addstr("[ ] "); 71 } 72 printw("%s: %s %s %s\n", mixer_path, 73 dev.name, dev.version, dev.config); 74 if (selected_mixer == i) 75 attroff(A_STANDOUT); 76 } 77 } 78 79 void 80 draw_control(struct aiomixer *aio, 81 struct aiomixer_control *control, bool selected) 82 { 83 struct mixer_ctrl value; 84 85 value.dev = control->info.index; 86 value.type = control->info.type; 87 if (value.type == AUDIO_MIXER_VALUE) 88 value.un.value.num_channels = control->info.un.v.num_channels; 89 90 if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0) 91 err(EXIT_FAILURE, "failed to read from mixer device"); 92 93 wclear(control->widgetpad); 94 if (selected) 95 wattron(control->widgetpad, A_STANDOUT); 96 if (value.type == AUDIO_MIXER_VALUE) 97 wprintw(control->widgetpad, "%s (%s)\n", 98 control->info.label.name, control->info.un.v.units.name); 99 else 100 wprintw(control->widgetpad, "%s\n", control->info.label.name); 101 if (selected) 102 wattroff(control->widgetpad, A_STANDOUT); 103 104 switch (value.type) { 105 case AUDIO_MIXER_ENUM: 106 draw_enum(control, value.un.ord, selected); 107 break; 108 case AUDIO_MIXER_SET: 109 draw_set(control, value.un.mask); 110 break; 111 case AUDIO_MIXER_VALUE: 112 draw_levels(control, &value.un.value, 113 aio->channels_unlocked, selected); 114 break; 115 } 116 117 wprintw(control->widgetpad, "\n"); 118 } 119 120 void 121 draw_screen(struct aiomixer *aio) 122 { 123 int i, max_y; 124 125 /* Clear any leftovers if the screen changed. */ 126 if (aio->widgets_resized) { 127 aio->widgets_resized = false; 128 max_y = getmaxy(stdscr); 129 for (i = 3; i < max_y; ++i) { 130 mvprintw(i, 0, "\n"); 131 } 132 } 133 134 wnoutrefresh(stdscr); 135 wnoutrefresh(aio->header); 136 wnoutrefresh(aio->classbar); 137 max_y = aio->classes[aio->curclass].height + 1; 138 max_y -= aio->class_scroll_y; 139 if (max_y > (getmaxy(stdscr) - 3)) 140 max_y = getmaxy(stdscr) - 3; 141 pnoutrefresh(aio->classes[aio->curclass].widgetpad, 142 aio->class_scroll_y, 0, 143 3, 0, 144 max_y, getmaxx(stdscr)); 145 doupdate(); 146 } 147 148 static int 149 get_enum_color(const char *name) 150 { 151 if (strcmp(name, AudioNon) == 0) { 152 return COLOR_ENUM_ON; 153 } 154 if (strcmp(name, AudioNoff) == 0) { 155 return COLOR_ENUM_OFF; 156 } 157 158 return COLOR_ENUM_MISC; 159 } 160 161 static void 162 draw_enum(struct aiomixer_control *control, int ord, bool selected) 163 { 164 struct audio_mixer_enum *e; 165 int color = COLOR_ENUM_MISC; 166 int i; 167 168 for (i = 0; i < control->info.un.e.num_mem; ++i) { 169 e = &control->info.un.e; 170 if (ord == e->member[i].ord && selected) { 171 if (termattrs() & A_BOLD) 172 wattron(control->widgetpad, A_BOLD); 173 else 174 wattron(control->widgetpad, A_STANDOUT); 175 } 176 waddch(control->widgetpad, '['); 177 if (ord == e->member[i].ord) { 178 if (has_colors()) { 179 color = get_enum_color(e->member[i].label.name); 180 wattron(control->widgetpad, 181 COLOR_PAIR(color)); 182 } else { 183 waddch(control->widgetpad, '*'); 184 } 185 } 186 wprintw(control->widgetpad, "%s", e->member[i].label.name); 187 if (ord == control->info.un.e.member[i].ord) { 188 if (has_colors()) { 189 wattroff(control->widgetpad, 190 COLOR_PAIR(color)); 191 } 192 } 193 waddch(control->widgetpad, ']'); 194 if (ord == e->member[i].ord && selected) { 195 if (termattrs() & A_BOLD) 196 wattroff(control->widgetpad, A_BOLD); 197 else 198 wattroff(control->widgetpad, A_STANDOUT); 199 } 200 if (i != (e->num_mem - 1)) 201 waddstr(control->widgetpad, ", "); 202 } 203 waddch(control->widgetpad, '\n'); 204 } 205 206 static void 207 draw_set(struct aiomixer_control *control, int mask) 208 { 209 int i; 210 211 for (i = 0; i < control->info.un.s.num_mem; ++i) { 212 waddch(control->widgetpad, '['); 213 if (mask & control->info.un.s.member[i].mask) { 214 if (has_colors()) { 215 wattron(control->widgetpad, 216 COLOR_PAIR(COLOR_SET_SELECTED)); 217 } 218 waddch(control->widgetpad, '*'); 219 if (has_colors()) { 220 wattroff(control->widgetpad, 221 COLOR_PAIR(COLOR_SET_SELECTED)); 222 } 223 } else { 224 waddch(control->widgetpad, ' '); 225 } 226 waddstr(control->widgetpad, "] "); 227 if (control->setindex == i) { 228 if (termattrs() & A_BOLD) 229 wattron(control->widgetpad, A_BOLD); 230 else 231 wattron(control->widgetpad, A_STANDOUT); 232 } 233 wprintw(control->widgetpad, "%s", 234 control->info.un.s.member[i].label.name); 235 if (control->setindex == i) { 236 if (termattrs() & A_BOLD) 237 wattroff(control->widgetpad, A_BOLD); 238 else 239 wattroff(control->widgetpad, A_STANDOUT); 240 } 241 if (i != (control->info.un.s.num_mem - 1)) 242 waddstr(control->widgetpad, ", "); 243 } 244 } 245 246 static void 247 draw_levels(struct aiomixer_control *control, 248 const struct mixer_level *levels, bool channels_unlocked, bool selected) 249 { 250 int i; 251 int j, nchars; 252 253 for (i = 0; i < control->info.un.v.num_channels; ++i) { 254 if ((selected && !channels_unlocked) || 255 (control->setindex == i && channels_unlocked)) { 256 if (termattrs() & A_BOLD) 257 wattron(control->widgetpad, A_BOLD); 258 else 259 wattron(control->widgetpad, A_STANDOUT); 260 } 261 wprintw(control->widgetpad, "[%3u/%3u ", 262 levels->level[i], AUDIO_MAX_GAIN); 263 if (has_colors()) { 264 wattron(control->widgetpad, 265 COLOR_PAIR(COLOR_LEVELS)); 266 } 267 nchars = (levels->level[i] * 268 (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN; 269 for (j = 0; j < nchars; ++j) 270 waddch(control->widgetpad, '*'); 271 if (has_colors()) { 272 wattroff(control->widgetpad, 273 COLOR_PAIR(COLOR_LEVELS)); 274 } 275 nchars = getmaxx(control->widgetpad) - 11 - nchars; 276 for (j = 0; j < nchars; ++j) 277 waddch(control->widgetpad, ' '); 278 wprintw(control->widgetpad, "]\n"); 279 if ((selected && !channels_unlocked) || 280 (control->setindex == i && channels_unlocked)) { 281 if (termattrs() & A_BOLD) 282 wattroff(control->widgetpad, A_BOLD); 283 else 284 wattroff(control->widgetpad, A_STANDOUT); 285 } 286 } 287 } 288 289 void 290 draw_classbar(struct aiomixer *aio) 291 { 292 unsigned int i; 293 294 wmove(aio->classbar, 0, 0); 295 296 for (i = 0; i < aio->numclasses; ++i) { 297 if (aio->curclass == i) 298 wattron(aio->classbar, A_STANDOUT); 299 wprintw(aio->classbar, "[%u:%s]", 300 i + 1, aio->classes[i].name); 301 if (aio->curclass == i) 302 wattroff(aio->classbar, A_STANDOUT); 303 waddch(aio->classbar, ' '); 304 } 305 306 wprintw(aio->classbar, "\n\n"); 307 } 308 309 void 310 draw_header(struct aiomixer *aio) 311 { 312 wprintw(aio->header, "\n"); 313 mvwaddstr(aio->header, 0, 314 getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer") + 1, 315 "NetBSD audio mixer"); 316 317 if (aio->mixerdev.version[0] != '\0') { 318 mvwprintw(aio->header, 0, 0, "%s %s", 319 aio->mixerdev.name, aio->mixerdev.version); 320 } else { 321 mvwprintw(aio->header, 0, 0, "%s", aio->mixerdev.name); 322 } 323 } 324 325 void 326 create_widgets(struct aiomixer *aio) 327 { 328 size_t i, j; 329 struct aiomixer_class *class; 330 struct aiomixer_control *control; 331 332 aio->header = newwin(1, getmaxx(stdscr), 0, 0); 333 if (aio->header == NULL) 334 errx(EXIT_FAILURE, "failed to create window"); 335 336 aio->classbar = newwin(2, getmaxx(stdscr), 1, 0); 337 if (aio->classbar == NULL) 338 errx(EXIT_FAILURE, "failed to create window"); 339 340 for (i = 0; i < aio->numclasses; ++i) { 341 class = &aio->classes[i]; 342 class->height = 0; 343 class->widgetpad = newpad(4 * __arraycount(class->controls), 344 getmaxx(stdscr)); 345 if (class->widgetpad == NULL) 346 errx(EXIT_FAILURE, "failed to create curses pad"); 347 for (j = 0; j < class->numcontrols; ++j) { 348 control = &class->controls[j]; 349 switch (control->info.type) { 350 case AUDIO_MIXER_VALUE: 351 control->height = 2 + 352 control->info.un.v.num_channels; 353 break; 354 case AUDIO_MIXER_ENUM: 355 case AUDIO_MIXER_SET: 356 control->height = 3; 357 break; 358 } 359 control->widgetpad = subpad(class->widgetpad, 360 control->height, getmaxx(stdscr), 361 class->height, 0); 362 if (control->widgetpad == NULL) 363 errx(EXIT_FAILURE, "failed to create curses pad"); 364 control->widget_y = class->height; 365 class->height += control->height; 366 } 367 wresize(class->widgetpad, class->height, getmaxx(stdscr)); 368 } 369 370 aio->last_max_x = getmaxx(stdscr); 371 } 372 373 void 374 resize_widgets(struct aiomixer *aio) 375 { 376 size_t i, j; 377 struct aiomixer_class *class; 378 struct aiomixer_control *control; 379 int max_x; 380 381 max_x = getmaxx(stdscr); 382 383 if (aio->last_max_x != max_x) { 384 aio->last_max_x = max_x; 385 wresize(aio->header, 1, max_x); 386 wresize(aio->classbar, 2, max_x); 387 388 for (i = 0; i < aio->numclasses; ++i) { 389 class = &aio->classes[i]; 390 wresize(class->widgetpad, class->height, max_x); 391 for (j = 0; j < class->numcontrols; ++j) { 392 control = &class->controls[j]; 393 wresize(control->widgetpad, 394 control->height, max_x); 395 } 396 } 397 } 398 399 aio->widgets_resized = true; 400 } 401