1 /* $NetBSD: draw.c,v 1.10 2023/06/29 19:06:54 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, bool); 41 static void draw_set(struct aiomixer_control *, int, bool); 42 static void draw_levels(struct aiomixer_control *, 43 const struct mixer_level *, bool, 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, aio->use_colour); 107 break; 108 case AUDIO_MIXER_SET: 109 draw_set(control, value.un.mask, aio->use_colour); 110 break; 111 case AUDIO_MIXER_VALUE: 112 draw_levels(control, &value.un.value, 113 aio->channels_unlocked, selected, aio->use_colour); 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, 163 bool selected, bool colour) 164 { 165 struct audio_mixer_enum *e; 166 int color = COLOR_ENUM_MISC; 167 int i; 168 169 for (i = 0; i < control->info.un.e.num_mem; ++i) { 170 e = &control->info.un.e; 171 if (ord == e->member[i].ord && selected) { 172 if (termattrs() & A_BOLD) 173 wattron(control->widgetpad, A_BOLD); 174 else 175 wattron(control->widgetpad, A_STANDOUT); 176 } 177 waddch(control->widgetpad, '['); 178 if (ord == e->member[i].ord) { 179 if (colour) { 180 color = get_enum_color(e->member[i].label.name); 181 wattron(control->widgetpad, 182 COLOR_PAIR(color)); 183 } else { 184 waddch(control->widgetpad, '*'); 185 } 186 } 187 wprintw(control->widgetpad, "%s", e->member[i].label.name); 188 if (ord == control->info.un.e.member[i].ord) { 189 if (colour) { 190 wattroff(control->widgetpad, 191 COLOR_PAIR(color)); 192 } 193 } 194 waddch(control->widgetpad, ']'); 195 if (ord == e->member[i].ord && selected) { 196 if (termattrs() & A_BOLD) 197 wattroff(control->widgetpad, A_BOLD); 198 else 199 wattroff(control->widgetpad, A_STANDOUT); 200 } 201 if (i != (e->num_mem - 1)) 202 waddstr(control->widgetpad, ", "); 203 } 204 waddch(control->widgetpad, '\n'); 205 } 206 207 static void 208 draw_set(struct aiomixer_control *control, int mask, bool colour) 209 { 210 int i; 211 212 for (i = 0; i < control->info.un.s.num_mem; ++i) { 213 waddch(control->widgetpad, '['); 214 if (mask & control->info.un.s.member[i].mask) { 215 if (colour) { 216 wattron(control->widgetpad, 217 COLOR_PAIR(COLOR_SET_SELECTED)); 218 } 219 waddch(control->widgetpad, '*'); 220 if (colour) { 221 wattroff(control->widgetpad, 222 COLOR_PAIR(COLOR_SET_SELECTED)); 223 } 224 } else { 225 waddch(control->widgetpad, ' '); 226 } 227 waddstr(control->widgetpad, "] "); 228 if (control->setindex == i) { 229 if (termattrs() & A_BOLD) 230 wattron(control->widgetpad, A_BOLD); 231 else 232 wattron(control->widgetpad, A_STANDOUT); 233 } 234 wprintw(control->widgetpad, "%s", 235 control->info.un.s.member[i].label.name); 236 if (control->setindex == i) { 237 if (termattrs() & A_BOLD) 238 wattroff(control->widgetpad, A_BOLD); 239 else 240 wattroff(control->widgetpad, A_STANDOUT); 241 } 242 if (i != (control->info.un.s.num_mem - 1)) 243 waddstr(control->widgetpad, ", "); 244 } 245 } 246 247 static void 248 draw_levels(struct aiomixer_control *control, 249 const struct mixer_level *levels, bool channels_unlocked, 250 bool selected, bool colour) 251 { 252 int i; 253 int j, nchars; 254 255 for (i = 0; i < control->info.un.v.num_channels; ++i) { 256 if ((selected && !channels_unlocked) || 257 (control->setindex == i && channels_unlocked)) { 258 if (termattrs() & A_BOLD) 259 wattron(control->widgetpad, A_BOLD); 260 else 261 wattron(control->widgetpad, A_STANDOUT); 262 } 263 wprintw(control->widgetpad, "[%3u/%3u ", 264 levels->level[i], AUDIO_MAX_GAIN); 265 if (colour) { 266 wattron(control->widgetpad, 267 COLOR_PAIR(COLOR_LEVELS)); 268 } 269 nchars = (levels->level[i] * 270 (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN; 271 for (j = 0; j < nchars; ++j) 272 waddch(control->widgetpad, '*'); 273 if (colour) { 274 wattroff(control->widgetpad, 275 COLOR_PAIR(COLOR_LEVELS)); 276 } 277 nchars = getmaxx(control->widgetpad) - 11 - nchars; 278 for (j = 0; j < nchars; ++j) 279 waddch(control->widgetpad, ' '); 280 wprintw(control->widgetpad, "]\n"); 281 if ((selected && !channels_unlocked) || 282 (control->setindex == i && channels_unlocked)) { 283 if (termattrs() & A_BOLD) 284 wattroff(control->widgetpad, A_BOLD); 285 else 286 wattroff(control->widgetpad, A_STANDOUT); 287 } 288 } 289 } 290 291 void 292 draw_classbar(struct aiomixer *aio) 293 { 294 unsigned int i; 295 296 wmove(aio->classbar, 0, 0); 297 298 for (i = 0; i < aio->numclasses; ++i) { 299 if (aio->curclass == i) 300 wattron(aio->classbar, A_STANDOUT); 301 wprintw(aio->classbar, "[%u:%s]", 302 i + 1, aio->classes[i].name); 303 if (aio->curclass == i) 304 wattroff(aio->classbar, A_STANDOUT); 305 waddch(aio->classbar, ' '); 306 } 307 308 wprintw(aio->classbar, "\n\n"); 309 } 310 311 void 312 draw_header(struct aiomixer *aio) 313 { 314 wprintw(aio->header, "\n"); 315 mvwaddstr(aio->header, 0, 316 getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer"), 317 "NetBSD audio mixer"); 318 319 if (aio->mixerdev.version[0] != '\0') { 320 mvwprintw(aio->header, 0, 0, "%s %s", 321 aio->mixerdev.name, aio->mixerdev.version); 322 } else { 323 mvwprintw(aio->header, 0, 0, "%s", aio->mixerdev.name); 324 } 325 } 326 327 void 328 create_widgets(struct aiomixer *aio) 329 { 330 size_t i, j; 331 struct aiomixer_class *class; 332 struct aiomixer_control *control; 333 334 aio->header = newwin(1, getmaxx(stdscr), 0, 0); 335 if (aio->header == NULL) 336 errx(EXIT_FAILURE, "failed to create window"); 337 338 aio->classbar = newwin(2, getmaxx(stdscr), 1, 0); 339 if (aio->classbar == NULL) 340 errx(EXIT_FAILURE, "failed to create window"); 341 342 for (i = 0; i < aio->numclasses; ++i) { 343 class = &aio->classes[i]; 344 class->height = 0; 345 class->widgetpad = newpad(4 * __arraycount(class->controls), 346 getmaxx(stdscr)); 347 if (class->widgetpad == NULL) 348 errx(EXIT_FAILURE, "failed to create curses pad"); 349 for (j = 0; j < class->numcontrols; ++j) { 350 control = &class->controls[j]; 351 switch (control->info.type) { 352 case AUDIO_MIXER_VALUE: 353 control->height = 2 + 354 control->info.un.v.num_channels; 355 break; 356 case AUDIO_MIXER_ENUM: 357 case AUDIO_MIXER_SET: 358 control->height = 3; 359 break; 360 } 361 control->widgetpad = subpad(class->widgetpad, 362 control->height, getmaxx(stdscr), 363 class->height, 0); 364 if (control->widgetpad == NULL) 365 errx(EXIT_FAILURE, "failed to create curses pad"); 366 control->widget_y = class->height; 367 class->height += control->height; 368 } 369 wresize(class->widgetpad, class->height, getmaxx(stdscr)); 370 } 371 372 aio->last_max_x = getmaxx(stdscr); 373 } 374 375 void 376 resize_widgets(struct aiomixer *aio) 377 { 378 size_t i, j; 379 struct aiomixer_class *class; 380 struct aiomixer_control *control; 381 int max_x; 382 383 max_x = getmaxx(stdscr); 384 385 if (aio->last_max_x != max_x) { 386 aio->last_max_x = max_x; 387 wresize(aio->header, 1, max_x); 388 wresize(aio->classbar, 2, max_x); 389 390 for (i = 0; i < aio->numclasses; ++i) { 391 class = &aio->classes[i]; 392 wresize(class->widgetpad, class->height, max_x); 393 for (j = 0; j < class->numcontrols; ++j) { 394 control = &class->controls[j]; 395 wresize(control->widgetpad, 396 control->height, max_x); 397 } 398 } 399 } 400 401 aio->widgets_resized = true; 402 } 403