tizen 2.3.1 release
[external/alsa-lib.git] / src / control / namehint.c
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., 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         int cleanup_res = 0;
223
224         list->device_input = -1;
225         list->device_output = -1;
226         buf = malloc(BUF_SIZE);
227         if (buf == NULL)
228                 return -ENOMEM;
229         sprintf(buf, "%s.%s", base, name);
230         /* look for redirection */
231         if (snd_config_search(snd_config, buf, &cfg) >= 0 &&
232             snd_config_get_string(cfg, &str) >= 0 &&
233             ((strncmp(base, str, strlen(base)) == 0 &&
234              str[strlen(base)] == '.') || strchr(str, '.') == NULL))
235                 goto __skip_add;
236         if (list->card >= 0 && list->device >= 0)
237                 sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device);
238         else if (list->card >= 0)
239                 sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info));
240         else
241                 strcpy(buf, name);
242         eh = snd_lib_error;
243         snd_lib_error_set_handler(&zero_handler);
244         err = snd_config_search_definition(snd_config, base, buf, &res);
245         snd_lib_error_set_handler(eh);
246         if (err < 0)
247                 goto __skip_add;
248         cleanup_res = 1;
249         err = -EINVAL;
250         if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND)
251                 goto __cleanup;
252         if (snd_config_search(res, "type", NULL) < 0)
253                 goto __cleanup;
254
255 #if 0   /* for debug purposes */
256                 {
257                         snd_output_t *out;
258                         fprintf(stderr, "********* PCM '%s':\n", buf);
259                         snd_output_stdio_attach(&out, stderr, 0);
260                         snd_config_save(res, out);
261                         snd_output_close(out);
262                         fprintf(stderr, "\n");
263                 }
264 #endif
265
266         cfg1 = res;
267         level = 0;
268       __hint:
269         level++;
270         if (snd_config_search(cfg1, "type", &cfg) >= 0 &&
271             snd_config_get_string(cfg, &str) >= 0 &&
272             strcmp(str, "hw") == 0) {
273                 dev = 0;
274                 list->device_input = -1;
275                 list->device_output = -1;
276                 if (snd_config_search(cfg1, "device", &cfg) >= 0) {
277                         if (snd_config_get_integer(cfg, &dev) < 0) {
278                                 SNDERR("(%s) device must be an integer", buf);
279                                 err = -EINVAL;
280                                 goto __cleanup;
281                         }
282                 }
283         }
284         
285         if (snd_config_search(cfg1, "hint", &cfg) >= 0) {
286                 if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
287                         SNDERR("hint (%s) must be a compound", buf);
288                         err = -EINVAL;
289                         goto __cleanup;
290                 }
291                 if (level == 1 &&
292                     snd_config_search(cfg, "show", &n) >= 0 &&
293                     snd_config_get_bool(n) <= 0)
294                         goto __skip_add;
295                 if (buf1 == NULL &&
296                     snd_config_search(cfg, "description", &n) >= 0 &&
297                     snd_config_get_string(n, &str) >= 0) {
298                         buf1 = strdup(str);
299                         if (buf1 == NULL) {
300                                 err = -ENOMEM;
301                                 goto __cleanup;
302                         }
303                 }
304                 if (snd_config_search(cfg, "device", &n) >= 0) {
305                         if (snd_config_get_integer(n, &dev) < 0) {
306                                 SNDERR("(%s) device must be an integer", buf);
307                                 err = -EINVAL;
308                                 goto __cleanup;
309                         }
310                         list->device_input = dev;
311                         list->device_output = dev;
312                 }
313                 if (snd_config_search(cfg, "device_input", &n) >= 0) {
314                         if (snd_config_get_integer(n, &list->device_input) < 0) {
315                                 SNDERR("(%s) device_input must be an integer", buf);
316                                 err = -EINVAL;
317                                 goto __cleanup;
318                         }
319                         list->device_output = -1;
320                 }
321                 if (snd_config_search(cfg, "device_output", &n) >= 0) {
322                         if (snd_config_get_integer(n, &list->device_output) < 0) {
323                                 SNDERR("(%s) device_output must be an integer", buf);
324                                 err = -EINVAL;
325                                 goto __cleanup;
326                         }
327                 }
328         } else if (level == 1 && !list->show_all)
329                 goto __skip_add;
330         if (snd_config_search(cfg1, "slave", &cfg) >= 0 &&
331             snd_config_search(cfg, base, &cfg1) >= 0)
332                 goto __hint;
333         snd_config_delete(res);
334         res = NULL;
335         cleanup_res = 0;
336         if (strchr(buf, ':') != NULL)
337                 goto __ok;
338         /* find, if all parameters have a default, */
339         /* otherwise filter this definition */
340         eh = snd_lib_error;
341         snd_lib_error_set_handler(&zero_handler);
342         err = snd_config_search_alias_hooks(snd_config, base, buf, &res);
343         snd_lib_error_set_handler(eh);
344         if (err < 0)
345                 goto __cleanup;
346         if (snd_config_search(res, "@args", &cfg) >= 0) {
347                 snd_config_for_each(i, next, cfg) {
348                         if (snd_config_search(snd_config_iterator_entry(i),
349                                               "default", NULL) < 0) {
350                                 err = -EINVAL;
351                                 goto __cleanup;
352                         }
353                 }
354         }
355       __ok:
356         err = 0;
357       __cleanup:
358         if (err >= 0) {
359                 list->device = dev;
360                 str = list->card >= 0 ? get_dev_name(list) : NULL;
361                 if (str != NULL) {
362                         level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str);
363                         buf2 = realloc((char *)str, level + 1);
364                         if (buf2 != NULL) {
365                                 if (buf1 != NULL) {
366                                         str = strchr(buf2, '|');
367                                         if (str != NULL)
368                                                 memmove(buf2 + (level - strlen(str)), str, strlen(str));
369                                         else
370                                                 str = buf2 + strlen(buf2);
371                                         *(char *)str++ = '\n';
372                                         memcpy((char *)str, buf1, strlen(buf1));
373                                         buf2[level] = '\0';
374                                         free(buf1);
375                                 }
376                                 buf1 = buf2;
377                         } else {
378                                 free((char *)str);
379                         }
380                 } else if (list->device >= 0)
381                         goto __skip_add;
382                 err = hint_list_add(list, buf, buf1);
383         }
384       __skip_add:
385         if (res && cleanup_res)
386                 snd_config_delete(res);
387         if (buf1)
388                 free(buf1);
389         free(buf);
390         return err;
391 }
392
393 #ifndef DOC_HIDDEN
394 #define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn
395
396 typedef int (*next_devices_t)(snd_ctl_t *, int *);
397
398 static const next_devices_t next_devices[] = {
399         IFACE(CARD, NULL),
400         IFACE(HWDEP, snd_ctl_hwdep_next_device),
401         IFACE(MIXER, NULL),
402         IFACE(PCM, snd_ctl_pcm_next_device),
403         IFACE(RAWMIDI, snd_ctl_rawmidi_next_device),
404         IFACE(TIMER, NULL),
405         IFACE(SEQUENCER, NULL)
406 };
407 #endif
408
409 static int add_card(struct hint_list *list, int card)
410 {
411         int err, ok;
412         snd_config_t *conf, *n;
413         snd_config_iterator_t i, next;
414         const char *str;
415         char ctl_name[16];
416         snd_ctl_card_info_t *info;
417         int device, max_device = 0;
418         
419         snd_ctl_card_info_alloca(&info);
420         list->info = info;
421         err = snd_config_search(snd_config, list->siface, &conf);
422         if (err < 0)
423                 return err;
424         sprintf(ctl_name, "hw:%i", card);
425         err = snd_ctl_open(&list->ctl, ctl_name, 0);
426         if (err < 0)
427                 return err;
428         err = snd_ctl_card_info(list->ctl, info);
429         if (err < 0)
430                 goto __error;
431         snd_config_for_each(i, next, conf) {
432                 n = snd_config_iterator_entry(i);
433                 if (snd_config_get_id(n, &str) < 0)
434                         continue;
435                 
436                 if (next_devices[list->iface] != NULL) {
437                         list->card = card;
438                         device = max_device = -1;
439                         err = next_devices[list->iface](list->ctl, &device);
440                         if (device < 0)
441                                 err = -EINVAL;
442                         else
443                                 max_device = device;
444                         while (err >= 0 && device >= 0) {
445                                 err = next_devices[list->iface](list->ctl, &device);
446                                 if (err >= 0 && device > max_device)
447                                         max_device = device;
448                         }
449                         ok = 0;
450                         for (device = 0; err >= 0 && device <= max_device; device++) {
451                                 list->device = device;
452                                 err = try_config(list, list->siface, str);
453                                 if (err < 0)
454                                         break;
455                                 ok++;
456                         }
457                         if (ok)
458                                 continue;
459                 } else {
460                         err = -EINVAL;
461                 }
462                 if (err == -EXDEV)
463                         continue;
464                 if (err < 0) {
465                         list->card = card;
466                         list->device = -1;
467                         err = try_config(list, list->siface, str);
468                 }
469                 if (err == -ENOMEM)
470                         goto __error;
471         }
472         err = 0;
473       __error:
474         snd_ctl_close(list->ctl);
475         return err;
476 }
477
478 static int get_card_name(struct hint_list *list, int card)
479 {
480         char scard[16], *s;
481         int err;
482
483         free(list->cardname);
484         list->cardname = NULL;
485         err = snd_card_get_name(card, &list->cardname);
486         if (err <= 0)
487                 return 0;
488         sprintf(scard, " #%i", card);
489         s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1);
490         if (s == NULL)
491                 return -ENOMEM;
492         list->cardname = s;
493         return 0;
494 }
495
496 static int add_software_devices(struct hint_list *list)
497 {
498         int err;
499         snd_config_t *conf, *n;
500         snd_config_iterator_t i, next;
501         const char *str;
502
503         err = snd_config_search(snd_config, list->siface, &conf);
504         if (err < 0)
505                 return err;
506         snd_config_for_each(i, next, conf) {
507                 n = snd_config_iterator_entry(i);
508                 if (snd_config_get_id(n, &str) < 0)
509                         continue;
510                 list->card = -1;
511                 list->device = -1;
512                 err = try_config(list, list->siface, str);
513                 if (err == -ENOMEM)
514                         return -ENOMEM;
515         }
516         return 0;
517 }
518
519 /**
520  * \brief Get a set of device name hints
521  * \param card Card number or -1 (means all cards)
522  * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq")
523  * \param hints Result - array of device name hints
524  * \result zero if success, otherwise a negative error code
525  *
526  * hints will receive a NULL-terminated array of device name hints,
527  * which can be passed to #snd_device_name_get_hint to extract usable
528  * values. When no longer needed, hints should be passed to
529  * #snd_device_name_free_hint to release resources.
530  *
531  * User-defined hints are gathered from namehint.IFACE tree like:
532  *
533  * <code>
534  * namehint.pcm {<br>
535  *   myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
536  *   myplug "plug:front:Do all conversions for front speakers"<br>
537  * }
538  * </code>
539  *
540  * Note: The device description is separated with '|' char.
541  *
542  * Special variables: defaults.namehint.showall specifies if all device
543  * definitions are accepted (boolean type).
544  */
545 int snd_device_name_hint(int card, const char *iface, void ***hints)
546 {
547         struct hint_list list;
548         char ehints[24];
549         const char *str;
550         snd_config_t *conf;
551         snd_config_iterator_t i, next;
552         int err;
553
554         if (hints == NULL)
555                 return -EINVAL;
556         err = snd_config_update();
557         if (err < 0)
558                 return err;
559         list.list = NULL;
560         list.count = list.allocated = 0;
561         list.siface = iface;
562         if (strcmp(iface, "card") == 0)
563                 list.iface = SND_CTL_ELEM_IFACE_CARD;
564         else if (strcmp(iface, "pcm") == 0)
565                 list.iface = SND_CTL_ELEM_IFACE_PCM;
566         else if (strcmp(iface, "rawmidi") == 0)
567                 list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
568         else if (strcmp(iface, "timer") == 0)
569                 list.iface = SND_CTL_ELEM_IFACE_TIMER;
570         else if (strcmp(iface, "seq") == 0)
571                 list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
572         else if (strcmp(iface, "hwdep") == 0)
573                 list.iface = SND_CTL_ELEM_IFACE_HWDEP;
574         else if (strcmp(iface, "ctl") == 0)
575                 list.iface = SND_CTL_ELEM_IFACE_MIXER;
576         else
577                 return -EINVAL;
578         list.show_all = 0;
579         list.cardname = NULL;
580         if (snd_config_search(snd_config, "defaults.namehint.showall", &conf) >= 0)
581                 list.show_all = snd_config_get_bool(conf) > 0;
582         if (card >= 0) {
583                 err = get_card_name(&list, card);
584                 if (err >= 0)
585                         err = add_card(&list, card);
586         } else {
587                 add_software_devices(&list);
588                 err = snd_card_next(&card);
589                 if (err < 0)
590                         goto __error;
591                 while (card >= 0) {
592                         err = get_card_name(&list, card);
593                         if (err < 0)
594                                 goto __error;
595                         err = add_card(&list, card);
596                         if (err < 0)
597                                 goto __error;
598                         err = snd_card_next(&card);
599                         if (err < 0)
600                                 goto __error;
601                 }
602         }
603         sprintf(ehints, "namehint.%s", list.siface);
604         err = snd_config_search(snd_config, ehints, &conf);
605         if (err >= 0) {
606                 snd_config_for_each(i, next, conf) {
607                         if (snd_config_get_string(snd_config_iterator_entry(i),
608                                                   &str) < 0)
609                                 continue;
610                         err = hint_list_add(&list, str, NULL);
611                         if (err < 0)
612                                 goto __error;
613                 }
614         }
615         err = 0;
616       __error:
617         if (err < 0) {
618                 snd_device_name_free_hint((void **)list.list);
619                 if (list.cardname)
620                         free(list.cardname);
621                 return err;
622         } else {
623                 err = hint_list_add(&list, NULL, NULL);
624                 if (err < 0)
625                         goto __error;
626                 *hints = (void **)list.list;
627                 if (list.cardname)
628                         free(list.cardname);
629         }
630         return 0;
631 }
632
633 /**
634  * \brief Free a list of device name hints.
635  * \param hints List to free
636  * \result zero if success, otherwise a negative error code
637  */
638 int snd_device_name_free_hint(void **hints)
639 {
640         char **h;
641
642         if (hints == NULL)
643                 return 0;
644         h = (char **)hints;
645         while (*h) {
646                 free(*h);
647                 h++;
648         }
649         free(hints);
650         return 0;
651 }
652
653 /**
654  * \brief Extract a value from a hint
655  * \param hint A pointer to hint
656  * \param id Hint value to extract ("NAME", "DESC", or "IOID", see below)
657  * \result an allocated ASCII string if success, otherwise NULL
658  *
659  * List of valid IDs:
660  * NAME - name of device
661  * DESC - description of device
662  * IOID - input / output identification ("Input" or "Output"), NULL means both
663  *
664  * The return value should be freed when no longer needed.
665  */
666 char *snd_device_name_get_hint(const void *hint, const char *id)
667 {
668         const char *hint1 = (const char *)hint, *delim;
669         char *res;
670         unsigned size;
671
672         if (strlen(id) != 4)
673                 return NULL;
674         while (*hint1 != '\0') {
675                 delim = strchr(hint1, '|');
676                 if (memcmp(id, hint1, 4) != 0) {
677                         if (delim == NULL)
678                                 return NULL;
679                         hint1 = delim + 1;
680                         continue;
681                 } 
682                 if (delim == NULL)
683                         return strdup(hint1 + 4);
684                 size = delim - hint1 - 4;
685                 res = malloc(size + 1);
686                 if (res != NULL) {
687                         memcpy(res, hint1 + 4, size);
688                         res[size] = '\0';
689                 }
690                 return res;
691         }
692         return NULL;
693 }