1 /**
2 * \file control/namehint.c
3 * \brief Give device name hints
4 * \author Jaroslav Kysela <perex@perex.cz>
5 * \date 2006
6 */
7 /*
8 * Give device name hints - main file
9 * Copyright (c) 2006 by Jaroslav Kysela <perex@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 *
26 */
27
28 #include "local.h"
29
30 #ifndef DOC_HIDDEN
31 #define DEV_SKIP 9999 /* some non-existing device number */
32 struct hint_list {
33 char **list;
34 unsigned int count;
35 unsigned int allocated;
36 const char *siface;
37 snd_ctl_elem_iface_t iface;
38 snd_ctl_t *ctl;
39 snd_ctl_card_info_t *info;
40 int card;
41 int device;
42 long device_input;
43 long device_output;
44 int stream;
45 int show_all;
46 char *cardname;
47 };
48 #endif
49
hint_list_add(struct hint_list * list,const char * name,const char * description)50 static int hint_list_add(struct hint_list *list,
51 const char *name,
52 const char *description)
53 {
54 char *x;
55
56 if (list->count + 1 >= list->allocated) {
57 char **n = realloc(list->list, (list->allocated + 10) * sizeof(char *));
58 if (n == NULL)
59 return -ENOMEM;
60 memset(n + list->allocated, 0, 10 * sizeof(*n));
61 list->allocated += 10;
62 list->list = n;
63 }
64 if (name == NULL) {
65 x = NULL;
66 } else {
67 x = malloc(4 + strlen(name) + (description != NULL ? (4 + strlen(description) + 1) : 0) + 1);
68 if (x == NULL)
69 return -ENOMEM;
70 memcpy(x, "NAME", 4);
71 strcpy(x + 4, name);
72 if (description != NULL) {
73 strcat(x, "|DESC");
74 strcat(x, description);
75 }
76 }
77 list->list[list->count++] = x;
78 return 0;
79 }
80
81 /**
82 * Add a namehint from string given in a user configuration file
83 */
hint_list_add_custom(struct hint_list * list,const char * entry)84 static int hint_list_add_custom(struct hint_list *list,
85 const char *entry)
86 {
87 int err;
88 const char *sep;
89 char *name;
90
91 assert(entry);
92
93 sep = strchr(entry, '|');
94 if (sep == NULL)
95 return hint_list_add(list, entry, NULL);
96
97 name = strndup(entry, sep - entry);
98 if (name == NULL)
99 return -ENOMEM;
100
101 err = hint_list_add(list, name, sep + 1);
102 free(name);
103 return err;
104 }
105
zero_handler(const char * file ATTRIBUTE_UNUSED,int line ATTRIBUTE_UNUSED,const char * function ATTRIBUTE_UNUSED,int err ATTRIBUTE_UNUSED,const char * fmt ATTRIBUTE_UNUSED,va_list arg ATTRIBUTE_UNUSED)106 static void zero_handler(const char *file ATTRIBUTE_UNUSED,
107 int line ATTRIBUTE_UNUSED,
108 const char *function ATTRIBUTE_UNUSED,
109 int err ATTRIBUTE_UNUSED,
110 const char *fmt ATTRIBUTE_UNUSED,
111 va_list arg ATTRIBUTE_UNUSED)
112 {
113 }
114
get_dev_name1(struct hint_list * list,char ** res,int device,int stream)115 static int get_dev_name1(struct hint_list *list, char **res, int device,
116 int stream)
117 {
118 *res = NULL;
119 if (device < 0 || device == DEV_SKIP)
120 return 0;
121 switch (list->iface) {
122 #ifdef BUILD_HWDEP
123 case SND_CTL_ELEM_IFACE_HWDEP:
124 {
125 snd_hwdep_info_t info = {0};
126 snd_hwdep_info_set_device(&info, device);
127 if (snd_ctl_hwdep_info(list->ctl, &info) < 0)
128 return 0;
129 *res = strdup(snd_hwdep_info_get_name(&info));
130 return 0;
131 }
132 #endif
133 #ifdef BUILD_PCM
134 case SND_CTL_ELEM_IFACE_PCM:
135 {
136 snd_pcm_info_t info = {0};
137 snd_pcm_info_set_device(&info, device);
138 snd_pcm_info_set_stream(&info, stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
139 if (snd_ctl_pcm_info(list->ctl, &info) < 0)
140 return 0;
141 switch (snd_pcm_info_get_class(&info)) {
142 case SND_PCM_CLASS_MODEM:
143 case SND_PCM_CLASS_DIGITIZER:
144 return -ENODEV;
145 default:
146 break;
147 }
148 *res = strdup(snd_pcm_info_get_name(&info));
149 return 0;
150 }
151 #endif
152 #ifdef BUILD_RAWMIDI
153 case SND_CTL_ELEM_IFACE_RAWMIDI:
154 {
155 snd_rawmidi_info_t info = {0};
156 snd_rawmidi_info_set_device(&info, device);
157 snd_rawmidi_info_set_stream(&info, stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
158 if (snd_ctl_rawmidi_info(list->ctl, &info) < 0)
159 return 0;
160 *res = strdup(snd_rawmidi_info_get_name(&info));
161 return 0;
162 }
163 #endif
164 default:
165 return 0;
166 }
167 }
168
get_dev_name(struct hint_list * list)169 static char *get_dev_name(struct hint_list *list)
170 {
171 char *str1, *str2, *res;
172 int device;
173
174 device = list->device_input >= 0 ? list->device_input : list->device;
175 if (get_dev_name1(list, &str1, device, 1) < 0)
176 return NULL;
177 device = list->device_output >= 0 ? list->device_output : list->device;
178 if (get_dev_name1(list, &str2, device, 0) < 0) {
179 if (str1)
180 free(str1);
181 return NULL;
182 }
183 if (str1 != NULL || str2 != NULL) {
184 if (str1 != NULL && str2 != NULL) {
185 if (strcmp(str1, str2) == 0) {
186 res = malloc(strlen(list->cardname) + strlen(str2) + 3);
187 if (res != NULL) {
188 strcpy(res, list->cardname);
189 strcat(res, ", ");
190 strcat(res, str2);
191 }
192 } else {
193 res = malloc(strlen(list->cardname) + strlen(str2) + strlen(str1) + 6);
194 if (res != NULL) {
195 strcpy(res, list->cardname);
196 strcat(res, ", ");
197 strcat(res, str2);
198 strcat(res, " / ");
199 strcat(res, str1);
200 }
201 }
202 free(str2);
203 free(str1);
204 return res;
205 } else {
206 if (str1 != NULL) {
207 str2 = "Input";
208 } else {
209 str1 = str2;
210 str2 = "Output";
211 }
212 res = malloc(strlen(list->cardname) + strlen(str1) + 19);
213 if (res == NULL) {
214 free(str1);
215 return NULL;
216 }
217 strcpy(res, list->cardname);
218 strcat(res, ", ");
219 strcat(res, str1);
220 strcat(res, "|IOID");
221 strcat(res, str2);
222 free(str1);
223 return res;
224 }
225 }
226 /* if the specified device doesn't exist, skip this entry */
227 if (list->device >= 0 || list->device_input >= 0 || list->device_output >= 0)
228 return NULL;
229 return strdup(list->cardname);
230 }
231
232 #ifndef DOC_HIDDEN
233 #define BUF_SIZE 128
234 #endif
235
try_config(snd_config_t * config,struct hint_list * list,const char * base,const char * name)236 static int try_config(snd_config_t *config,
237 struct hint_list *list,
238 const char *base,
239 const char *name)
240 {
241 snd_local_error_handler_t eh;
242 snd_config_t *res = NULL, *cfg, *cfg1, *n;
243 snd_config_iterator_t i, next;
244 char *buf, *buf1 = NULL, *buf2;
245 const char *str;
246 int err = 0, level;
247 long dev = list->device;
248 int cleanup_res = 0;
249
250 list->device_input = -1;
251 list->device_output = -1;
252 buf = malloc(BUF_SIZE);
253 if (buf == NULL)
254 return -ENOMEM;
255 sprintf(buf, "%s.%s", base, name);
256 /* look for redirection */
257 if (snd_config_search(config, buf, &cfg) >= 0 &&
258 snd_config_get_string(cfg, &str) >= 0 &&
259 ((strncmp(base, str, strlen(base)) == 0 &&
260 str[strlen(base)] == '.') || strchr(str, '.') == NULL))
261 goto __skip_add;
262 if (list->card >= 0 && list->device >= 0)
263 sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device);
264 else if (list->card >= 0)
265 sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info));
266 else
267 strcpy(buf, name);
268 eh = snd_lib_error_set_local(&zero_handler);
269 err = snd_config_search_definition(config, base, buf, &res);
270 snd_lib_error_set_local(eh);
271 if (err < 0)
272 goto __skip_add;
273 cleanup_res = 1;
274 err = -EINVAL;
275 if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND)
276 goto __cleanup;
277 if (snd_config_search(res, "type", NULL) < 0)
278 goto __cleanup;
279
280 #if 0 /* for debug purposes */
281 {
282 snd_output_t *out;
283 fprintf(stderr, "********* PCM '%s':\n", buf);
284 snd_output_stdio_attach(&out, stderr, 0);
285 snd_config_save(res, out);
286 snd_output_close(out);
287 fprintf(stderr, "\n");
288 }
289 #endif
290
291 cfg1 = res;
292 level = 0;
293 __hint:
294 level++;
295 if (snd_config_search(cfg1, "type", &cfg) >= 0 &&
296 snd_config_get_string(cfg, &str) >= 0 &&
297 strcmp(str, "hw") == 0) {
298 if (snd_config_search(cfg1, "device", &cfg) >= 0) {
299 if (snd_config_get_integer(cfg, &dev) < 0) {
300 SNDERR("(%s) device must be an integer", buf);
301 err = -EINVAL;
302 goto __cleanup;
303 }
304 }
305 }
306
307 if (snd_config_search(cfg1, "hint", &cfg) >= 0) {
308 if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
309 SNDERR("hint (%s) must be a compound", buf);
310 err = -EINVAL;
311 goto __cleanup;
312 }
313 if (list->card < 0 &&
314 snd_config_search(cfg, "omit_noargs", &n) >= 0 &&
315 snd_config_get_bool(n) > 0)
316 goto __skip_add;
317 if (level == 1 &&
318 snd_config_search(cfg, "show", &n) >= 0 &&
319 snd_config_get_bool(n) <= 0)
320 goto __skip_add;
321 if (buf1 == NULL &&
322 snd_config_search(cfg, "description", &n) >= 0 &&
323 snd_config_get_string(n, &str) >= 0) {
324 buf1 = strdup(str);
325 if (buf1 == NULL) {
326 err = -ENOMEM;
327 goto __cleanup;
328 }
329 }
330 if (snd_config_search(cfg, "device", &n) >= 0) {
331 if (snd_config_get_integer(n, &dev) < 0) {
332 SNDERR("(%s) device must be an integer", buf);
333 err = -EINVAL;
334 goto __cleanup;
335 }
336 list->device_input = dev;
337 list->device_output = dev;
338 }
339 if (snd_config_search(cfg, "device_input", &n) >= 0) {
340 if (snd_config_get_integer(n, &list->device_input) < 0) {
341 SNDERR("(%s) device_input must be an integer", buf);
342 err = -EINVAL;
343 goto __cleanup;
344 }
345 /* skip the counterpart if only a single direction is defined */
346 if (list->device_output < 0)
347 list->device_output = DEV_SKIP;
348 }
349 if (snd_config_search(cfg, "device_output", &n) >= 0) {
350 if (snd_config_get_integer(n, &list->device_output) < 0) {
351 SNDERR("(%s) device_output must be an integer", buf);
352 err = -EINVAL;
353 goto __cleanup;
354 }
355 /* skip the counterpart if only a single direction is defined */
356 if (list->device_input < 0)
357 list->device_input = DEV_SKIP;
358 }
359 } else if (level == 1 && !list->show_all)
360 goto __skip_add;
361 if (snd_config_search(cfg1, "slave", &cfg) >= 0 &&
362 snd_config_search(cfg, base, &cfg1) >= 0)
363 goto __hint;
364 snd_config_delete(res);
365 res = NULL;
366 cleanup_res = 0;
367 if (strchr(buf, ':') != NULL)
368 goto __ok;
369 /* find, if all parameters have a default, */
370 /* otherwise filter this definition */
371 eh = snd_lib_error_set_local(&zero_handler);
372 err = snd_config_search_alias_hooks(config, base, buf, &res);
373 snd_lib_error_set_local(eh);
374 if (err < 0)
375 goto __cleanup;
376 if (snd_config_search(res, "@args", &cfg) >= 0) {
377 snd_config_for_each(i, next, cfg) {
378 /* skip the argument list */
379 if (snd_config_get_id(snd_config_iterator_entry(i), &str) < 0)
380 continue;
381 while (*str && *str >= '0' && *str <= '9') str++;
382 if (*str == '\0')
383 continue;
384 /* the argument definition must have the default */
385 if (snd_config_search(snd_config_iterator_entry(i),
386 "default", NULL) < 0) {
387 err = -EINVAL;
388 goto __cleanup;
389 }
390 }
391 }
392 __ok:
393 err = 0;
394 __cleanup:
395 if (err >= 0) {
396 list->device = dev;
397 str = list->card >= 0 ? get_dev_name(list) : NULL;
398 if (str != NULL) {
399 level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str);
400 buf2 = realloc((char *)str, level + 1);
401 if (buf2 != NULL) {
402 if (buf1 != NULL) {
403 str = strchr(buf2, '|');
404 if (str != NULL)
405 memmove(buf2 + (level - strlen(str)), str, strlen(str));
406 else
407 str = buf2 + strlen(buf2);
408 *(char *)str++ = '\n';
409 memcpy((char *)str, buf1, strlen(buf1));
410 buf2[level] = '\0';
411 free(buf1);
412 }
413 buf1 = buf2;
414 } else {
415 free((char *)str);
416 }
417 } else if (list->device >= 0)
418 goto __skip_add;
419 err = hint_list_add(list, buf, buf1);
420 }
421 __skip_add:
422 if (res && cleanup_res)
423 snd_config_delete(res);
424 if (buf1)
425 free(buf1);
426 free(buf);
427 return err;
428 }
429
430 #ifndef DOC_HIDDEN
431 #define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn
432
433 typedef int (*next_devices_t)(snd_ctl_t *, int *);
434
435 static const next_devices_t next_devices[] = {
436 IFACE(CARD, NULL),
437 IFACE(HWDEP, snd_ctl_hwdep_next_device),
438 IFACE(MIXER, NULL),
439 IFACE(PCM, snd_ctl_pcm_next_device),
440 IFACE(RAWMIDI, snd_ctl_rawmidi_next_device),
441 IFACE(TIMER, NULL),
442 IFACE(SEQUENCER, NULL)
443 };
444 #endif
445
add_card(snd_config_t * config,snd_config_t * rw_config,struct hint_list * list,int card)446 static int add_card(snd_config_t *config, snd_config_t *rw_config, struct hint_list *list, int card)
447 {
448 int err, ok;
449 snd_config_t *conf, *n;
450 snd_config_iterator_t i, next;
451 const char *str;
452 char ctl_name[16];
453 snd_ctl_card_info_t info = {0};
454 int device, max_device = 0;
455
456 list->info = &info;
457 err = snd_config_search(config, list->siface, &conf);
458 if (err < 0)
459 return err;
460 sprintf(ctl_name, "hw:%i", card);
461 err = snd_ctl_open(&list->ctl, ctl_name, 0);
462 if (err < 0)
463 return err;
464 err = snd_ctl_card_info(list->ctl, &info);
465 if (err < 0)
466 goto __error;
467 snd_config_for_each(i, next, conf) {
468 n = snd_config_iterator_entry(i);
469 if (snd_config_get_id(n, &str) < 0)
470 continue;
471
472 if (next_devices[list->iface] != NULL) {
473 list->card = card;
474 device = max_device = -1;
475 err = next_devices[list->iface](list->ctl, &device);
476 if (device < 0)
477 err = -EINVAL;
478 else
479 max_device = device;
480 while (err >= 0 && device >= 0) {
481 err = next_devices[list->iface](list->ctl, &device);
482 if (err >= 0 && device > max_device)
483 max_device = device;
484 }
485 ok = 0;
486 for (device = 0; err >= 0 && device <= max_device; device++) {
487 list->device = device;
488 err = try_config(rw_config, list, list->siface, str);
489 if (err < 0)
490 break;
491 ok++;
492 }
493 if (ok)
494 continue;
495 } else {
496 err = -EINVAL;
497 }
498 if (err == -EXDEV)
499 continue;
500 if (err < 0) {
501 list->card = card;
502 list->device = -1;
503 err = try_config(rw_config, list, list->siface, str);
504 }
505 if (err == -ENOMEM)
506 goto __error;
507 }
508 err = 0;
509 __error:
510 snd_ctl_close(list->ctl);
511 return err;
512 }
513
get_card_name(struct hint_list * list,int card)514 static int get_card_name(struct hint_list *list, int card)
515 {
516 char scard[16], *s;
517 int err;
518
519 free(list->cardname);
520 list->cardname = NULL;
521 err = snd_card_get_name(card, &list->cardname);
522 if (err <= 0)
523 return 0;
524 sprintf(scard, " #%i", card);
525 s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1);
526 if (s == NULL)
527 return -ENOMEM;
528 list->cardname = s;
529 return 0;
530 }
531
add_software_devices(snd_config_t * config,snd_config_t * rw_config,struct hint_list * list)532 static int add_software_devices(snd_config_t *config, snd_config_t *rw_config,
533 struct hint_list *list)
534 {
535 int err;
536 snd_config_t *conf, *n;
537 snd_config_iterator_t i, next;
538 const char *str;
539
540 err = snd_config_search(config, list->siface, &conf);
541 if (err < 0)
542 return err;
543 snd_config_for_each(i, next, conf) {
544 n = snd_config_iterator_entry(i);
545 if (snd_config_get_id(n, &str) < 0)
546 continue;
547 list->card = -1;
548 list->device = -1;
549 err = try_config(rw_config, list, list->siface, str);
550 if (err == -ENOMEM)
551 return -ENOMEM;
552 }
553 return 0;
554 }
555
556 /**
557 * \brief Get a set of device name hints
558 * \param card Card number or -1 (means all cards)
559 * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq")
560 * \param hints Result - array of device name hints
561 * \result zero if success, otherwise a negative error code
562 *
563 * hints will receive a NULL-terminated array of device name hints,
564 * which can be passed to #snd_device_name_get_hint to extract usable
565 * values. When no longer needed, hints should be passed to
566 * #snd_device_name_free_hint to release resources.
567 *
568 * User-defined hints are gathered from namehint.IFACE tree like:
569 *
570 * <code>
571 * namehint.pcm [<br>
572 * myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
573 * myplug "plug:front|Do all conversions for front speakers"<br>
574 * ]
575 * </code>
576 *
577 * Note: The device description is separated with '|' char.
578 *
579 * Special variables: defaults.namehint.showall specifies if all device
580 * definitions are accepted (boolean type).
581 */
snd_device_name_hint(int card,const char * iface,void *** hints)582 int snd_device_name_hint(int card, const char *iface, void ***hints)
583 {
584 struct hint_list list;
585 char ehints[24];
586 const char *str;
587 snd_config_t *conf, *local_config = NULL, *local_config_rw = NULL;
588 snd_config_update_t *local_config_update = NULL;
589 snd_config_iterator_t i, next;
590 int err;
591
592 if (hints == NULL)
593 return -EINVAL;
594 err = snd_config_update_r(&local_config, &local_config_update, NULL);
595 if (err < 0)
596 return err;
597 err = snd_config_copy(&local_config_rw, local_config);
598 if (err < 0)
599 return err;
600 list.list = NULL;
601 list.count = list.allocated = 0;
602 list.siface = iface;
603 list.show_all = 0;
604 list.cardname = NULL;
605 if (strcmp(iface, "card") == 0)
606 list.iface = SND_CTL_ELEM_IFACE_CARD;
607 else if (strcmp(iface, "pcm") == 0)
608 list.iface = SND_CTL_ELEM_IFACE_PCM;
609 else if (strcmp(iface, "rawmidi") == 0)
610 list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
611 else if (strcmp(iface, "timer") == 0)
612 list.iface = SND_CTL_ELEM_IFACE_TIMER;
613 else if (strcmp(iface, "seq") == 0)
614 list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
615 else if (strcmp(iface, "hwdep") == 0)
616 list.iface = SND_CTL_ELEM_IFACE_HWDEP;
617 else if (strcmp(iface, "ctl") == 0)
618 list.iface = SND_CTL_ELEM_IFACE_MIXER;
619 else {
620 err = -EINVAL;
621 goto __error;
622 }
623
624 if (snd_config_search(local_config, "defaults.namehint.showall", &conf) >= 0)
625 list.show_all = snd_config_get_bool(conf) > 0;
626 if (card >= 0) {
627 err = get_card_name(&list, card);
628 if (err >= 0)
629 err = add_card(local_config, local_config_rw, &list, card);
630 } else {
631 add_software_devices(local_config, local_config_rw, &list);
632 err = snd_card_next(&card);
633 if (err < 0)
634 goto __error;
635 while (card >= 0) {
636 err = get_card_name(&list, card);
637 if (err < 0)
638 goto __error;
639 err = add_card(local_config, local_config_rw, &list, card);
640 if (err < 0)
641 goto __error;
642 err = snd_card_next(&card);
643 if (err < 0)
644 goto __error;
645 }
646 }
647 sprintf(ehints, "namehint.%s", list.siface);
648 err = snd_config_search(local_config, ehints, &conf);
649 if (err >= 0) {
650 snd_config_for_each(i, next, conf) {
651 if (snd_config_get_string(snd_config_iterator_entry(i),
652 &str) < 0)
653 continue;
654 err = hint_list_add_custom(&list, str);
655 if (err < 0)
656 goto __error;
657 }
658 }
659 err = 0;
660 __error:
661 /* add an empty entry if nothing has been added yet; the caller
662 * expects non-NULL return
663 */
664 if (!err && !list.list)
665 err = hint_list_add(&list, NULL, NULL);
666 if (err < 0)
667 snd_device_name_free_hint((void **)list.list);
668 else
669 *hints = (void **)list.list;
670 free(list.cardname);
671 if (local_config_rw)
672 snd_config_delete(local_config_rw);
673 if (local_config)
674 snd_config_delete(local_config);
675 if (local_config_update)
676 snd_config_update_free(local_config_update);
677 return err;
678 }
679
680 /**
681 * \brief Free a list of device name hints.
682 * \param hints List to free
683 * \result zero if success, otherwise a negative error code
684 */
snd_device_name_free_hint(void ** hints)685 int snd_device_name_free_hint(void **hints)
686 {
687 char **h;
688
689 if (hints == NULL)
690 return 0;
691 h = (char **)hints;
692 while (*h) {
693 free(*h);
694 h++;
695 }
696 free(hints);
697 return 0;
698 }
699
700 /**
701 * \brief Extract a value from a hint
702 * \param hint A pointer to hint
703 * \param id Hint value to extract ("NAME", "DESC", or "IOID", see below)
704 * \result an allocated ASCII string if success, otherwise NULL
705 *
706 * List of valid IDs:
707 * NAME - name of device
708 * DESC - description of device
709 * IOID - input / output identification ("Input" or "Output"), NULL means both
710 *
711 * The return value should be freed when no longer needed.
712 */
snd_device_name_get_hint(const void * hint,const char * id)713 char *snd_device_name_get_hint(const void *hint, const char *id)
714 {
715 const char *hint1 = (const char *)hint, *delim;
716 char *res;
717 unsigned size;
718
719 if (strlen(id) != 4)
720 return NULL;
721 while (*hint1 != '\0') {
722 delim = strchr(hint1, '|');
723 if (memcmp(id, hint1, 4) != 0) {
724 if (delim == NULL)
725 return NULL;
726 hint1 = delim + 1;
727 continue;
728 }
729 if (delim == NULL)
730 return strdup(hint1 + 4);
731 size = delim - hint1 - 4;
732 res = malloc(size + 1);
733 if (res != NULL) {
734 memcpy(res, hint1 + 4, size);
735 res[size] = '\0';
736 }
737 return res;
738 }
739 return NULL;
740 }
741