namehint: Another fix to properly evaluate hw devices
[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 Return string list with 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 string with device name hints
524  * \result zero if success, otherwise a negative error code
525  *
526  * Note: The device description is separated with '|' char.
527  *
528  * User defined hints are gathered from namehint.IFACE tree like:
529  *
530  * <code>
531  * namehint.pcm {<br>
532  *   myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
533  *   myplug "plug:front:Do all conversions for front speakers"<br>
534  * }
535  * </code>
536  *
537  * Special variables: defaults.namehint.showall specifies if all device
538  * definitions are accepted (boolean type).
539  */
540 int snd_device_name_hint(int card, const char *iface, void ***hints)
541 {
542         struct hint_list list;
543         char ehints[24];
544         const char *str;
545         snd_config_t *conf;
546         snd_config_iterator_t i, next;
547         int err;
548
549         if (hints == NULL)
550                 return -EINVAL;
551         err = snd_config_update();
552         if (err < 0)
553                 return err;
554         list.list = NULL;
555         list.count = list.allocated = 0;
556         list.siface = iface;
557         if (strcmp(iface, "card") == 0)
558                 list.iface = SND_CTL_ELEM_IFACE_CARD;
559         else if (strcmp(iface, "pcm") == 0)
560                 list.iface = SND_CTL_ELEM_IFACE_PCM;
561         else if (strcmp(iface, "rawmidi") == 0)
562                 list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
563         else if (strcmp(iface, "timer") == 0)
564                 list.iface = SND_CTL_ELEM_IFACE_TIMER;
565         else if (strcmp(iface, "seq") == 0)
566                 list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
567         else if (strcmp(iface, "hwdep") == 0)
568                 list.iface = SND_CTL_ELEM_IFACE_HWDEP;
569         else if (strcmp(iface, "ctl") == 0)
570                 list.iface = SND_CTL_ELEM_IFACE_MIXER;
571         else
572                 return -EINVAL;
573         list.show_all = 0;
574         list.cardname = NULL;
575         if (snd_config_search(snd_config, "defaults.namehint.showall", &conf) >= 0)
576                 list.show_all = snd_config_get_bool(conf) > 0;
577         if (card >= 0) {
578                 err = get_card_name(&list, card);
579                 if (err >= 0)
580                         err = add_card(&list, card);
581         } else {
582                 add_software_devices(&list);
583                 err = snd_card_next(&card);
584                 if (err < 0)
585                         goto __error;
586                 while (card >= 0) {
587                         err = get_card_name(&list, card);
588                         if (err < 0)
589                                 goto __error;
590                         err = add_card(&list, card);
591                         if (err < 0)
592                                 goto __error;
593                         err = snd_card_next(&card);
594                         if (err < 0)
595                                 goto __error;
596                 }
597         }
598         sprintf(ehints, "namehint.%s", list.siface);
599         err = snd_config_search(snd_config, ehints, &conf);
600         if (err >= 0) {
601                 snd_config_for_each(i, next, conf) {
602                         if (snd_config_get_string(snd_config_iterator_entry(i),
603                                                   &str) < 0)
604                                 continue;
605                         err = hint_list_add(&list, str, NULL);
606                         if (err < 0)
607                                 goto __error;
608                 }
609         }
610         err = 0;
611       __error:
612         if (err < 0) {
613                 snd_device_name_free_hint((void **)list.list);
614                 if (list.cardname)
615                         free(list.cardname);
616                 return err;
617         } else {
618                 err = hint_list_add(&list, NULL, NULL);
619                 if (err < 0)
620                         goto __error;
621                 *hints = (void **)list.list;
622                 if (list.cardname)
623                         free(list.cardname);
624         }
625         return 0;
626 }
627
628 /**
629  * \brief Free a string list with device name hints.
630  * \param hints A string list to free
631  * \result zero if success, otherwise a negative error code
632  */
633 int snd_device_name_free_hint(void **hints)
634 {
635         char **h;
636
637         if (hints == NULL)
638                 return 0;
639         h = (char **)hints;
640         while (*h) {
641                 free(*h);
642                 h++;
643         }
644         free(hints);
645         return 0;
646 }
647
648 /**
649  * \brief Get a hint Free a string list with device name hints.
650  * \param hint A pointer to hint
651  * \param id Hint ID (see bellow)
652  * \result an allocated ASCII string if success, otherwise NULL
653  *
654  * List of valid IDs:
655  * NAME - name of device
656  * DESC - description of device
657  * IOID - input / output identification (Input or Output strings),
658  *        not present (NULL) means both
659  */
660 char *snd_device_name_get_hint(const void *hint, const char *id)
661 {
662         const char *hint1 = (const char *)hint, *delim;
663         char *res;
664         unsigned size;
665
666         if (strlen(id) != 4)
667                 return NULL;
668         while (*hint1 != '\0') {
669                 delim = strchr(hint1, '|');
670                 if (memcmp(id, hint1, 4) != 0) {
671                         if (delim == NULL)
672                                 return NULL;
673                         hint1 = delim + 1;
674                         continue;
675                 } 
676                 if (delim == NULL)
677                         return strdup(hint1 + 4);
678                 size = delim - hint1 - 4;
679                 res = malloc(size + 1);
680                 if (res != NULL) {
681                         memcpy(res, hint1 + 4, size);
682                         res[size] = '\0';
683                 }
684                 return res;
685         }
686         return NULL;
687 }