1 /** 2 * \file control/namehint.c 3 * \brief Give device name hints 4 * \author Jaroslav Kysela <perex (at) perex.cz> 5 * \date 2006 6 */ 7 /* 8 * Give device name hints - main file 9 * Copyright (c) 2006 by Jaroslav Kysela <perex (at) perex.cz> 10 * 11 * 12 * This library is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU Lesser General Public License as 14 * published by the Free Software Foundation; either version 2.1 of 15 * the License, or (at your option) any later version. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU Lesser General Public License for more details. 21 * 22 * You should have received a copy of the GNU Lesser General Public 23 * License along with this library; if not, write to the Free Software 24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25 * 26 */ 27 28 #include "local.h" 29 30 #ifndef DOC_HIDDEN 31 struct hint_list { 32 char **list; 33 unsigned int count; 34 unsigned int allocated; 35 const char *siface; 36 snd_ctl_elem_iface_t iface; 37 snd_ctl_t *ctl; 38 snd_ctl_card_info_t *info; 39 int card; 40 int device; 41 long device_input; 42 long device_output; 43 int stream; 44 int show_all; 45 char *cardname; 46 }; 47 #endif 48 49 static int hint_list_add(struct hint_list *list, 50 const char *name, 51 const char *description) 52 { 53 char *x; 54 55 if (list->count == list->allocated) { 56 char **n = realloc(list->list, (list->allocated + 10) * sizeof(char *)); 57 if (n == NULL) 58 return -ENOMEM; 59 list->allocated += 10; 60 list->list = n; 61 } 62 if (name == NULL) { 63 x = NULL; 64 } else { 65 x = malloc(4 + strlen(name) + (description != NULL ? (4 + strlen(description) + 1) : 0) + 1); 66 if (x == NULL) 67 return -ENOMEM; 68 memcpy(x, "NAME", 4); 69 strcpy(x + 4, name); 70 if (description != NULL) { 71 strcat(x, "|DESC"); 72 strcat(x, description); 73 } 74 } 75 list->list[list->count++] = x; 76 return 0; 77 } 78 79 static void zero_handler(const char *file ATTRIBUTE_UNUSED, 80 int line ATTRIBUTE_UNUSED, 81 const char *function ATTRIBUTE_UNUSED, 82 int err ATTRIBUTE_UNUSED, 83 const char *fmt ATTRIBUTE_UNUSED, ...) 84 { 85 } 86 87 static int get_dev_name1(struct hint_list *list, char **res, int device, 88 int stream) 89 { 90 *res = NULL; 91 if (device < 0) 92 return 0; 93 switch (list->iface) { 94 #ifdef BUILD_HWDEP 95 case SND_CTL_ELEM_IFACE_HWDEP: 96 { 97 snd_hwdep_info_t *info; 98 snd_hwdep_info_alloca(&info); 99 snd_hwdep_info_set_device(info, device); 100 if (snd_ctl_hwdep_info(list->ctl, info) < 0) 101 return 0; 102 *res = strdup(snd_hwdep_info_get_name(info)); 103 return 0; 104 } 105 #endif 106 #ifdef BUILD_PCM 107 case SND_CTL_ELEM_IFACE_PCM: 108 { 109 snd_pcm_info_t *info; 110 snd_pcm_info_alloca(&info); 111 snd_pcm_info_set_device(info, device); 112 snd_pcm_info_set_stream(info, stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK); 113 if (snd_ctl_pcm_info(list->ctl, info) < 0) 114 return 0; 115 switch (snd_pcm_info_get_class(info)) { 116 case SND_PCM_CLASS_MODEM: 117 case SND_PCM_CLASS_DIGITIZER: 118 return -ENODEV; 119 default: 120 break; 121 } 122 *res = strdup(snd_pcm_info_get_name(info)); 123 return 0; 124 } 125 #endif 126 #ifdef BUILD_RAWMIDI 127 case SND_CTL_ELEM_IFACE_RAWMIDI: 128 { 129 snd_rawmidi_info_t *info; 130 snd_rawmidi_info_alloca(&info); 131 snd_rawmidi_info_set_device(info, device); 132 snd_rawmidi_info_set_stream(info, stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT); 133 if (snd_ctl_rawmidi_info(list->ctl, info) < 0) 134 return 0; 135 *res = strdup(snd_rawmidi_info_get_name(info)); 136 return 0; 137 } 138 #endif 139 default: 140 return 0; 141 } 142 } 143 144 static char *get_dev_name(struct hint_list *list) 145 { 146 char *str1, *str2, *res; 147 int device; 148 149 device = list->device_input >= 0 ? list->device_input : list->device; 150 if (get_dev_name1(list, &str1, device, 1) < 0) 151 return NULL; 152 device = list->device_output >= 0 ? list->device_output : list->device; 153 if (get_dev_name1(list, &str2, device, 0) < 0) { 154 if (str1) 155 free(str1); 156 return NULL; 157 } 158 if (str1 != NULL || str2 != NULL) { 159 if (str1 != NULL && str2 != NULL) { 160 if (strcmp(str1, str2) == 0) { 161 res = malloc(strlen(list->cardname) + strlen(str2) + 3); 162 if (res != NULL) { 163 strcpy(res, list->cardname); 164 strcat(res, ", "); 165 strcat(res, str2); 166 } 167 } else { 168 res = malloc(strlen(list->cardname) + strlen(str2) + strlen(str1) + 6); 169 if (res != NULL) { 170 strcpy(res, list->cardname); 171 strcat(res, ", "); 172 strcat(res, str2); 173 strcat(res, " / "); 174 strcat(res, str1); 175 } 176 } 177 free(str2); 178 free(str1); 179 return res; 180 } else { 181 if (str1 != NULL) { 182 str2 = "Input"; 183 } else { 184 str1 = str2; 185 str2 = "Output"; 186 } 187 res = malloc(strlen(list->cardname) + strlen(str1) + 19); 188 if (res == NULL) { 189 free(str1); 190 return NULL; 191 } 192 strcpy(res, list->cardname); 193 strcat(res, ", "); 194 strcat(res, str1); 195 strcat(res, "|IOID"); 196 strcat(res, str2); 197 free(str1); 198 return res; 199 } 200 } 201 /* if the specified device doesn't exist, skip this entry */ 202 if (list->device >= 0 || list->device_input >= 0 || list->device_output >= 0) 203 return NULL; 204 return strdup(list->cardname); 205 } 206 207 #ifndef DOC_HIDDEN 208 #define BUF_SIZE 128 209 #endif 210 211 static int try_config(struct hint_list *list, 212 const char *base, 213 const char *name) 214 { 215 snd_lib_error_handler_t eh; 216 snd_config_t *res = NULL, *cfg, *cfg1, *n; 217 snd_config_iterator_t i, next; 218 char *buf, *buf1 = NULL, *buf2; 219 const char *str; 220 int err = 0, level; 221 long dev = list->device; 222 223 list->device_input = -1; 224 list->device_output = -1; 225 buf = malloc(BUF_SIZE); 226 if (buf == NULL) 227 return -ENOMEM; 228 sprintf(buf, "%s.%s", base, name); 229 /* look for redirection */ 230 if (snd_config_search(snd_config, buf, &cfg) >= 0 && 231 snd_config_get_string(cfg, &str) >= 0 && 232 ((strncmp(base, str, strlen(base)) == 0 && 233 str[strlen(base)] == '.') || strchr(str, '.') == NULL)) 234 goto __skip_add; 235 if (list->card >= 0 && list->device >= 0) 236 sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device); 237 else if (list->card >= 0) 238 sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info)); 239 else 240 strcpy(buf, name); 241 eh = snd_lib_error; 242 snd_lib_error_set_handler(&zero_handler); 243 err = snd_config_search_definition(snd_config, base, buf, &res); 244 snd_lib_error_set_handler(eh); 245 if (err < 0) 246 goto __skip_add; 247 err = -EINVAL; 248 if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND) 249 goto __cleanup; 250 if (snd_config_search(res, "type", NULL) < 0) 251 goto __cleanup; 252 253 #if 0 /* for debug purposes */ 254 { 255 snd_output_t *out; 256 fprintf(stderr, "********* PCM '%s':\n", buf); 257 snd_output_stdio_attach(&out, stderr, 0); 258 snd_config_save(res, out); 259 snd_output_close(out); 260 fprintf(stderr, "\n"); 261 } 262 #endif 263 264 cfg1 = res; 265 level = 0; 266 __hint: 267 level++; 268 if (snd_config_search(cfg1, "type", &cfg) >= 0 && 269 snd_config_get_string(cfg, &str) >= 0 && 270 strcmp(str, "hw") == 0) { 271 dev = 0; 272 list->device_input = -1; 273 list->device_output = -1; 274 if (snd_config_search(cfg1, "device", &cfg) >= 0) { 275 if (snd_config_get_integer(cfg, &dev) < 0) { 276 SNDERR("(%s) device must be an integer", buf); 277 err = -EINVAL; 278 goto __cleanup; 279 } 280 } 281 } 282 283 if (snd_config_search(cfg1, "hint", &cfg) >= 0) { 284 if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { 285 SNDERR("hint (%s) must be a compound", buf); 286 err = -EINVAL; 287 goto __cleanup; 288 } 289 if (level == 1 && 290 snd_config_search(cfg, "show", &n) >= 0 && 291 snd_config_get_bool(n) <= 0) 292 goto __skip_add; 293 if (buf1 == NULL && 294 snd_config_search(cfg, "description", &n) >= 0 && 295 snd_config_get_string(n, &str) >= 0) { 296 buf1 = strdup(str); 297 if (buf1 == NULL) { 298 err = -ENOMEM; 299 goto __cleanup; 300 } 301 } 302 if (snd_config_search(cfg, "device", &n) >= 0) { 303 if (snd_config_get_integer(n, &dev) < 0) { 304 SNDERR("(%s) device must be an integer", buf); 305 err = -EINVAL; 306 goto __cleanup; 307 } 308 list->device_input = dev; 309 list->device_output = dev; 310 } 311 if (snd_config_search(cfg, "device_input", &n) >= 0) { 312 if (snd_config_get_integer(n, &list->device_input) < 0) { 313 SNDERR("(%s) device_input must be an integer", buf); 314 err = -EINVAL; 315 goto __cleanup; 316 } 317 list->device_output = -1; 318 } 319 if (snd_config_search(cfg, "device_output", &n) >= 0) { 320 if (snd_config_get_integer(n, &list->device_output) < 0) { 321 SNDERR("(%s) device_output must be an integer", buf); 322 err = -EINVAL; 323 goto __cleanup; 324 } 325 } 326 } else if (level == 1 && !list->show_all) 327 goto __skip_add; 328 if (snd_config_search(cfg1, "slave", &cfg) >= 0 && 329 snd_config_search(cfg, base, &cfg1) >= 0) 330 goto __hint; 331 snd_config_delete(res); 332 res = NULL; 333 if (strchr(buf, ':') != NULL) 334 goto __ok; 335 /* find, if all parameters have a default, */ 336 /* otherwise filter this definition */ 337 eh = snd_lib_error; 338 snd_lib_error_set_handler(&zero_handler); 339 err = snd_config_search_alias_hooks(snd_config, base, buf, &res); 340 snd_lib_error_set_handler(eh); 341 if (err < 0) 342 goto __cleanup; 343 if (snd_config_search(res, "@args", &cfg) >= 0) { 344 snd_config_for_each(i, next, cfg) { 345 if (snd_config_search(snd_config_iterator_entry(i), 346 "default", NULL) < 0) { 347 err = -EINVAL; 348 goto __cleanup; 349 } 350 } 351 } 352 __ok: 353 err = 0; 354 __cleanup: 355 if (err >= 0) { 356 list->device = dev; 357 str = list->card >= 0 ? get_dev_name(list) : NULL; 358 if (str != NULL) { 359 level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str); 360 buf2 = realloc((char *)str, level + 1); 361 if (buf2 != NULL) { 362 if (buf1 != NULL) { 363 str = strchr(buf2, '|'); 364 if (str != NULL) 365 memmove(buf2 + (level - strlen(str)), str, strlen(str)); 366 else 367 str = buf2 + strlen(buf2); 368 *(char *)str++ = '\n'; 369 memcpy((char *)str, buf1, strlen(buf1)); 370 buf2[level] = '\0'; 371 free(buf1); 372 } 373 buf1 = buf2; 374 } else { 375 free((char *)str); 376 } 377 } else if (list->device >= 0) 378 goto __skip_add; 379 err = hint_list_add(list, buf, buf1); 380 } 381 __skip_add: 382 if (res) 383 snd_config_delete(res); 384 if (buf1) 385 free(buf1); 386 free(buf); 387 return err; 388 } 389 390 #ifndef DOC_HIDDEN 391 #define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn 392 393 typedef int (*next_devices_t)(snd_ctl_t *, int *); 394 395 static const next_devices_t next_devices[] = { 396 IFACE(CARD, NULL), 397 IFACE(HWDEP, snd_ctl_hwdep_next_device), 398 IFACE(MIXER, NULL), 399 IFACE(PCM, snd_ctl_pcm_next_device), 400 IFACE(RAWMIDI, snd_ctl_rawmidi_next_device), 401 IFACE(TIMER, NULL), 402 IFACE(SEQUENCER, NULL) 403 }; 404 #endif 405 406 static int add_card(struct hint_list *list, int card) 407 { 408 int err, ok; 409 snd_config_t *conf, *n; 410 snd_config_iterator_t i, next; 411 const char *str; 412 char ctl_name[16]; 413 snd_ctl_card_info_t *info; 414 415 snd_ctl_card_info_alloca(&info); 416 list->info = info; 417 err = snd_config_search(snd_config, list->siface, &conf); 418 if (err < 0) 419 return err; 420 sprintf(ctl_name, "hw:%i", card); 421 err = snd_ctl_open(&list->ctl, ctl_name, 0); 422 if (err < 0) 423 return err; 424 err = snd_ctl_card_info(list->ctl, info); 425 if (err < 0) 426 goto __error; 427 snd_config_for_each(i, next, conf) { 428 n = snd_config_iterator_entry(i); 429 if (snd_config_get_id(n, &str) < 0) 430 continue; 431 if (next_devices[list->iface] != NULL) { 432 list->card = card; 433 list->device = -1; 434 err = next_devices[list->iface](list->ctl, &list->device); 435 if (list->device < 0) 436 err = -EINVAL; 437 ok = 0; 438 while (err >= 0 && list->device >= 0) { 439 err = try_config(list, list->siface, str); 440 if (err < 0) 441 break; 442 err = next_devices[list->iface](list->ctl, &list->device); 443 ok++; 444 } 445 if (ok) 446 continue; 447 } else { 448 err = -EINVAL; 449 } 450 if (err == -EXDEV) 451 continue; 452 if (err < 0) { 453 list->device = -1; 454 err = try_config(list, list->siface, str); 455 } 456 if (err < 0) { 457 list->card = -1; 458 err = try_config(list, list->siface, str); 459 } 460 if (err == -ENOMEM) 461 goto __error; 462 } 463 err = 0; 464 __error: 465 snd_ctl_close(list->ctl); 466 return err; 467 } 468 469 static int get_card_name(struct hint_list *list, int card) 470 { 471 char scard[16], *s; 472 int err; 473 474 err = snd_card_get_name(card, &list->cardname); 475 if (err <= 0) 476 return 0; 477 sprintf(scard, " #%i", card); 478 s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1); 479 if (s == NULL) 480 return -ENOMEM; 481 list->cardname = s; 482 return 0; 483 } 484 485 /** 486 * \brief Return string list with device name hints. 487 * \param card Card number or -1 (means all cards) 488 * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq") 489 * \param hints Result - array of string with device name hints 490 * \result zero if success, otherwise a negative error code 491 * 492 * Note: The device description is separated with '|' char. 493 * 494 * User defined hints are gathered from namehint.IFACE tree like: 495 * 496 * <code> 497 * namehint.pcm {<br> 498 * myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br> 499 * myplug "plug:front:Do all conversions for front speakers"<br> 500 * } 501 * </code> 502 * 503 * Special variables: defaults.namehint.showall specifies if all device 504 * definitions are accepted (boolean type). 505 */ 506 int snd_device_name_hint(int card, const char *iface, void ***hints) 507 { 508 struct hint_list list; 509 char ehints[24]; 510 const char *str; 511 snd_config_t *conf; 512 snd_config_iterator_t i, next; 513 int err; 514 515 if (hints == NULL) 516 return -EINVAL; 517 err = snd_config_update(); 518 if (err < 0) 519 return err; 520 list.list = NULL; 521 list.count = list.allocated = 0; 522 list.siface = iface; 523 if (strcmp(iface, "card") == 0) 524 list.iface = SND_CTL_ELEM_IFACE_CARD; 525 else if (strcmp(iface, "pcm") == 0) 526 list.iface = SND_CTL_ELEM_IFACE_PCM; 527 else if (strcmp(iface, "rawmidi") == 0) 528 list.iface = SND_CTL_ELEM_IFACE_RAWMIDI; 529 else if (strcmp(iface, "timer") == 0) 530 list.iface = SND_CTL_ELEM_IFACE_TIMER; 531 else if (strcmp(iface, "seq") == 0) 532 list.iface = SND_CTL_ELEM_IFACE_SEQUENCER; 533 else if (strcmp(iface, "hwdep") == 0) 534 list.iface = SND_CTL_ELEM_IFACE_HWDEP; 535 else 536 return -EINVAL; 537 list.show_all = 0; 538 list.cardname = NULL; 539 if (snd_config_search(snd_config, "defaults.namehint.showall", &conf) >= 0) 540 list.show_all = snd_config_get_bool(conf) > 0; 541 if (card >= 0) { 542 err = get_card_name(&list, card); 543 if (err >= 0) 544 err = add_card(&list, card); 545 } else { 546 err = snd_card_next(&card); 547 if (err < 0) 548 goto __error; 549 while (card >= 0) { 550 err = get_card_name(&list, card); 551 if (err < 0) 552 goto __error; 553 err = add_card(&list, card); 554 if (err < 0) 555 goto __error; 556 err = snd_card_next(&card); 557 if (err < 0) 558 goto __error; 559 } 560 } 561 sprintf(ehints, "namehint.%s", list.siface); 562 err = snd_config_search(snd_config, ehints, &conf); 563 if (err >= 0) { 564 snd_config_for_each(i, next, conf) { 565 if (snd_config_get_string(snd_config_iterator_entry(i), 566 &str) < 0) 567 continue; 568 err = hint_list_add(&list, str, NULL); 569 if (err < 0) 570 goto __error; 571 } 572 } 573 err = 0; 574 __error: 575 if (err < 0) { 576 snd_device_name_free_hint((void **)list.list); 577 if (list.cardname) 578 free(list.cardname); 579 return err; 580 } else { 581 err = hint_list_add(&list, NULL, NULL); 582 if (err < 0) 583 goto __error; 584 *hints = (void **)list.list; 585 if (list.cardname) 586 free(list.cardname); 587 } 588 return 0; 589 } 590 591 /** 592 * \brief Free a string list with device name hints. 593 * \param hints A string list to free 594 * \result zero if success, otherwise a negative error code 595 */ 596 int snd_device_name_free_hint(void **hints) 597 { 598 char **h; 599 600 if (hints == NULL) 601 return 0; 602 h = (char **)hints; 603 while (*h) { 604 free(*h); 605 h++; 606 } 607 free(hints); 608 return 0; 609 } 610 611 /** 612 * \brief Get a hint Free a string list with device name hints. 613 * \param hint A pointer to hint 614 * \param id Hint ID (see bellow) 615 * \result an allocated ASCII string if success, otherwise NULL 616 * 617 * List of valid IDs: 618 * NAME - name of device 619 * DESC - description of device 620 * IOID - input / output identification (Input or Output strings), 621 * not present (NULL) means both 622 */ 623 char *snd_device_name_get_hint(const void *hint, const char *id) 624 { 625 const char *hint1 = (const char *)hint, *delim; 626 char *res; 627 unsigned size; 628 629 if (strlen(id) != 4) 630 return NULL; 631 while (*hint1 != '\0') { 632 delim = strchr(hint1, '|'); 633 if (memcmp(id, hint1, 4) != 0) { 634 if (delim == NULL) 635 return NULL; 636 hint1 = delim + 1; 637 continue; 638 } 639 if (delim == NULL) 640 return strdup(hint1 + 4); 641 size = delim - hint1 - 4; 642 res = malloc(size + 1); 643 if (res != NULL) { 644 memcpy(res, hint1 + 4, size); 645 res[size] = '\0'; 646 } 647 return res; 648 } 649 return NULL; 650 } 651