OSDN Git Service

Changed Jaroslav Kysela's e-mail from perex@suse.cz to perex@perex.cz
[android-x86/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)
88 {
89         *res = NULL;
90         if (list->device < 0)
91                 return 0;
92         switch (list->iface) {
93 #ifdef BUILD_HWDEP
94         case SND_CTL_ELEM_IFACE_HWDEP:
95                 {
96                         snd_hwdep_info_t *info;
97                         snd_hwdep_info_alloca(&info);
98                         snd_hwdep_info_set_device(info, list->device);
99                         if (snd_ctl_hwdep_info(list->ctl, info) < 0)
100                                 return 0;
101                         *res = strdup(snd_hwdep_info_get_name(info));
102                         return 0;
103                 }
104 #endif
105 #ifdef BUILD_PCM
106         case SND_CTL_ELEM_IFACE_PCM:
107                 {
108                         snd_pcm_info_t *info;
109                         snd_pcm_info_alloca(&info);
110                         snd_pcm_info_set_device(info, list->device);
111                         snd_pcm_info_set_stream(info, list->stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
112                         if (snd_ctl_pcm_info(list->ctl, info) < 0)
113                                 return 0;
114                         switch (snd_pcm_info_get_class(info)) {
115                         case SND_PCM_CLASS_MODEM:
116                         case SND_PCM_CLASS_DIGITIZER:
117                                 return -ENODEV;
118                         default:
119                                 break;
120                         }
121                         *res = strdup(snd_pcm_info_get_name(info));
122                         return 0;
123                 }
124 #endif
125 #ifdef BUILD_RAWMIDI
126         case SND_CTL_ELEM_IFACE_RAWMIDI:
127                 {
128                         snd_rawmidi_info_t *info;
129                         snd_rawmidi_info_alloca(&info);
130                         snd_rawmidi_info_set_device(info, list->device);
131                         snd_rawmidi_info_set_stream(info, list->stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
132                         if (snd_ctl_rawmidi_info(list->ctl, info) < 0)
133                                 return 0;
134                         *res = strdup(snd_rawmidi_info_get_name(info));
135                         return 0;
136                 }
137 #endif
138         default:
139                 return 0;
140         }
141 }
142
143 static char *get_dev_name(struct hint_list *list)
144 {
145         char *str1, *str2, *res;
146         
147         list->device = list->device_input >= 0 ? list->device_input : list->device;
148         list->stream = 1;
149         if (get_dev_name1(list, &str1) < 0)
150                 return NULL;
151         list->device = list->device_output >= 0 ? list->device_input : list->device;
152         list->stream = 0;
153         if (get_dev_name1(list, &str2) < 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         } else {
201                 return strdup(list->cardname);
202         }
203         return NULL;
204 }
205
206 #ifndef DOC_HIDDEN
207 #define BUF_SIZE 128
208 #endif
209
210 static int try_config(struct hint_list *list,
211                       const char *base,
212                       const char *name)
213 {
214         snd_lib_error_handler_t eh;
215         snd_config_t *res = NULL, *cfg, *cfg1, *n;
216         snd_config_iterator_t i, next;
217         char *buf, *buf1 = NULL, *buf2;
218         const char *str;
219         int err = 0, level;
220         long dev = list->device;
221
222         list->device_input = -1;
223         list->device_output = -1;
224         buf = malloc(BUF_SIZE);
225         if (buf == NULL)
226                 return -ENOMEM;
227         sprintf(buf, "%s.%s", base, name);
228         /* look for redirection */
229         if (snd_config_search(snd_config, buf, &cfg) >= 0 &&
230             snd_config_get_string(cfg, &str) >= 0 &&
231             ((strncmp(base, str, strlen(base)) == 0 &&
232              str[strlen(base)] == '.') || strchr(str, '.') == NULL))
233                 goto __skip_add;
234         if (list->card >= 0 && list->device >= 0)
235                 sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device);
236         else if (list->card >= 0)
237                 sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info));
238         else
239                 strcpy(buf, name);
240         eh = snd_lib_error;
241         snd_lib_error_set_handler(&zero_handler);
242         err = snd_config_search_definition(snd_config, base, buf, &res);
243         snd_lib_error_set_handler(eh);
244         if (err < 0)
245                 goto __skip_add;
246         err = -EINVAL;
247         if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND)
248                 goto __cleanup;
249         if (snd_config_search(res, "type", NULL) < 0)
250                 goto __cleanup;
251
252 #if 0   /* for debug purposes */
253                 {
254                         snd_output_t *out;
255                         fprintf(stderr, "********* PCM '%s':\n", buf);
256                         snd_output_stdio_attach(&out, stderr, 0);
257                         snd_config_save(res, out);
258                         snd_output_close(out);
259                         fprintf(stderr, "\n");
260                 }
261 #endif
262
263         cfg1 = res;
264         level = 0;
265       __hint:
266         level++;
267         if (snd_config_search(cfg1, "type", &cfg) >= 0 &&
268             snd_config_get_string(cfg, &str) >= 0 &&
269             strcmp(str, "hw") == 0) {
270                 dev = 0;
271                 list->device_input = -1;
272                 list->device_output = -1;
273                 if (snd_config_search(cfg1, "device", &cfg) >= 0) {
274                         if (snd_config_get_integer(cfg, &dev) < 0) {
275                                 SNDERR("(%s) device must be an integer", buf);
276                                 err = -EINVAL;
277                                 goto __cleanup;
278                         }
279                 }
280         }
281         
282         if (snd_config_search(cfg1, "hint", &cfg) >= 0) {
283                 if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
284                         SNDERR("hint (%s) must be a compound", buf);
285                         err = -EINVAL;
286                         goto __cleanup;
287                 }
288                 if (level == 1 &&
289                     snd_config_search(cfg, "show", &n) >= 0 &&
290                     snd_config_get_bool(n) <= 0)
291                         goto __skip_add;
292                 if (buf1 == NULL &&
293                     snd_config_search(cfg, "description", &n) >= 0 &&
294                     snd_config_get_string(n, &str) >= 0) {
295                         buf1 = strdup(str);
296                         if (buf1 == NULL) {
297                                 err = -ENOMEM;
298                                 goto __cleanup;
299                         }
300                 }
301                 if (snd_config_search(cfg, "device", &n) >= 0) {
302                         if (snd_config_get_integer(n, &dev) < 0) {
303                                 SNDERR("(%s) device must be an integer", buf);
304                                 err = -EINVAL;
305                                 goto __cleanup;
306                         }
307                         list->device_input = -1;
308                         list->device_output = -1;
309                 }
310                 if (snd_config_search(cfg, "device_input", &n) >= 0) {
311                         if (snd_config_get_integer(n, &list->device_input) < 0) {
312                                 SNDERR("(%s) device_input must be an integer", buf);
313                                 err = -EINVAL;
314                                 goto __cleanup;
315                         }
316                         list->device_output = -1;
317                 }
318                 if (snd_config_search(cfg, "device_output", &n) >= 0) {
319                         if (snd_config_get_integer(n, &list->device_output) < 0) {
320                                 SNDERR("(%s) device_output must be an integer", buf);
321                                 err = -EINVAL;
322                                 goto __cleanup;
323                         }
324                 }
325         } else if (level == 1 && !list->show_all)
326                 goto __skip_add;
327         if (snd_config_search(cfg1, "slave", &cfg) >= 0 &&
328             snd_config_search(cfg, base, &cfg1) >= 0)
329                 goto __hint;
330         snd_config_delete(res);
331         res = NULL;
332         if (strchr(buf, ':') != NULL)
333                 goto __ok;
334         /* find, if all parameters have a default, */
335         /* otherwise filter this definition */
336         eh = snd_lib_error;
337         snd_lib_error_set_handler(&zero_handler);
338         err = snd_config_search_alias_hooks(snd_config, base, buf, &res);
339         snd_lib_error_set_handler(eh);
340         if (err < 0)
341                 goto __cleanup;
342         if (snd_config_search(res, "@args", &cfg) >= 0) {
343                 snd_config_for_each(i, next, cfg) {
344                         if (snd_config_search(snd_config_iterator_entry(i),
345                                               "default", NULL) < 0) {
346                                 err = -EINVAL;
347                                 goto __cleanup;
348                         }
349                 }
350         }
351       __ok:
352         err = 0;
353       __cleanup:
354         if (err >= 0) {
355                 list->device = dev;
356                 str = list->card >= 0 ? get_dev_name(list) : NULL;
357                 if (str != NULL) {
358                         level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str);
359                         buf2 = realloc((char *)str, level + 1);
360                         if (buf2 != NULL) {
361                                 if (buf1 != NULL) {
362                                         str = strchr(buf2, '|');
363                                         if (str != NULL)
364                                                 memmove(buf2 + (level - strlen(str)), str, strlen(str));
365                                         else
366                                                 str = buf2 + strlen(buf2);
367                                         *(char *)str++ = '\n';
368                                         memcpy((char *)str, buf1, strlen(buf1));
369                                         buf2[level] = '\0';
370                                         free(buf1);
371                                 }
372                                 buf1 = buf2;
373                         } else {
374                                 free((char *)str);
375                         }
376                 } else if (list->device >= 0)
377                         goto __skip_add;
378                 err = hint_list_add(list, buf, buf1);
379         }
380       __skip_add:
381         if (res)
382                 snd_config_delete(res);
383         if (buf1)
384                 free(buf1);
385         free(buf);
386         return err;
387 }
388
389 #ifndef DOC_HIDDEN
390 #define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn
391
392 typedef int (*next_devices_t)(snd_ctl_t *, int *);
393
394 static next_devices_t next_devices[] = {
395         IFACE(CARD, NULL),
396         IFACE(HWDEP, snd_ctl_hwdep_next_device),
397         IFACE(MIXER, NULL),
398         IFACE(PCM, snd_ctl_pcm_next_device),
399         IFACE(RAWMIDI, snd_ctl_rawmidi_next_device),
400         IFACE(TIMER, NULL),
401         IFACE(SEQUENCER, NULL)
402 };
403 #endif
404
405 static int add_card(struct hint_list *list, int card)
406 {
407         int err, ok;
408         snd_config_t *conf, *n;
409         snd_config_iterator_t i, next;
410         const char *str;
411         char ctl_name[16];
412         snd_ctl_card_info_t *info;
413         
414         snd_ctl_card_info_alloca(&info);
415         list->info = info;
416         err = snd_config_search(snd_config, list->siface, &conf);
417         if (err < 0)
418                 return err;
419         sprintf(ctl_name, "hw:%i", card);
420         err = snd_ctl_open(&list->ctl, ctl_name, 0);
421         if (err < 0)
422                 return err;
423         err = snd_ctl_card_info(list->ctl, info);
424         if (err < 0)
425                 goto __error;
426         snd_config_for_each(i, next, conf) {
427                 n = snd_config_iterator_entry(i);
428                 if (snd_config_get_id(n, &str) < 0)
429                         continue;
430                 if (next_devices[list->iface] != NULL) {
431                         list->card = card;
432                         list->device = -1;
433                         err = next_devices[list->iface](list->ctl, &list->device);
434                         if (list->device < 0)
435                                 err = -EINVAL;
436                         ok = 0;
437                         while (err >= 0 && list->device >= 0) {
438                                 err = try_config(list, list->siface, str);
439                                 if (err < 0)
440                                         break;
441                                 err = next_devices[list->iface](list->ctl, &list->device);
442                                 ok++;
443                         }
444                         if (ok)
445                                 continue;
446                 } else {
447                         err = -EINVAL;
448                 }
449                 if (err == -EXDEV)
450                         continue;
451                 if (err < 0) {
452                         list->device = -1;
453                         err = try_config(list, list->siface, str);
454                 }
455                 if (err < 0) {
456                         list->card = -1;
457                         err = try_config(list, list->siface, str);
458                 }
459                 if (err == -ENOMEM)
460                         goto __error;
461         }
462         err = 0;
463       __error:
464         snd_ctl_close(list->ctl);
465         return err;
466 }
467
468 static int get_card_name(struct hint_list *list, int card)
469 {
470         char scard[16], *s;
471         int err;
472
473         err = snd_card_get_name(card, &list->cardname);
474         if (err <= 0)
475                 return 0;
476         sprintf(scard, " #%i", card);
477         s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1);
478         if (s == NULL)
479                 return -ENOMEM;
480         list->cardname = s;
481         return 0;
482 }
483
484 /**
485  * \brief Return string list with device name hints.
486  * \param card Card number or -1 (means all cards)
487  * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq")
488  * \param hints Result - array of string with device name hints
489  * \result zero if success, otherwise a negative error code
490  *
491  * Note: The device description is separated with '|' char.
492  *
493  * User defined hints are gathered from namehint.IFACE tree like:
494  *
495  * <code>
496  * namehint.pcm {<br>
497  *   myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
498  *   myplug "plug:front:Do all conversions for front speakers"<br>
499  * }
500  * </code>
501  *
502  * Special variables: defaults.namehint.showall specifies if all device
503  * definitions are accepted (boolean type).
504  */
505 int snd_device_name_hint(int card, const char *iface, void ***hints)
506 {
507         struct hint_list list;
508         char ehints[24];
509         const char *str;
510         snd_config_t *conf;
511         snd_config_iterator_t i, next;
512         int err;
513
514         if (hints == NULL)
515                 return -EINVAL;
516         err = snd_config_update();
517         if (err < 0)
518                 return err;
519         list.list = NULL;
520         list.count = list.allocated = 0;
521         list.siface = iface;
522         if (strcmp(iface, "card") == 0)
523                 list.iface = SND_CTL_ELEM_IFACE_CARD;
524         else if (strcmp(iface, "pcm") == 0)
525                 list.iface = SND_CTL_ELEM_IFACE_PCM;
526         else if (strcmp(iface, "rawmidi") == 0)
527                 list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
528         else if (strcmp(iface, "timer") == 0)
529                 list.iface = SND_CTL_ELEM_IFACE_TIMER;
530         else if (strcmp(iface, "seq") == 0)
531                 list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
532         else if (strcmp(iface, "hwdep") == 0)
533                 list.iface = SND_CTL_ELEM_IFACE_HWDEP;
534         else
535                 return -EINVAL;
536         list.show_all = 0;
537         list.cardname = NULL;
538         if (snd_config_search(snd_config, "defaults.namehint.showall", &conf) >= 0)
539                 list.show_all = snd_config_get_bool(conf) > 0;
540         if (card >= 0) {
541                 err = get_card_name(&list, card);
542                 if (err >= 0)
543                         err = add_card(&list, card);
544         } else {
545                 err = snd_card_next(&card);
546                 if (err < 0)
547                         goto __error;
548                 while (card >= 0) {
549                         err = get_card_name(&list, card);
550                         if (err < 0)
551                                 goto __error;
552                         err = add_card(&list, card);
553                         if (err < 0)
554                                 goto __error;
555                         err = snd_card_next(&card);
556                         if (err < 0)
557                                 goto __error;
558                 }
559         }
560         sprintf(ehints, "namehint.%s", list.siface);
561         err = snd_config_search(snd_config, ehints, &conf);
562         if (err >= 0) {
563                 snd_config_for_each(i, next, conf) {
564                         if (snd_config_get_string(snd_config_iterator_entry(i),
565                                                   &str) < 0)
566                                 continue;
567                         err = hint_list_add(&list, str, NULL);
568                         if (err < 0)
569                                 goto __error;
570                 }
571         }
572         err = 0;
573       __error:
574         if (err < 0) {
575                 snd_device_name_free_hint((void **)list.list);
576                 if (list.cardname)
577                         free(list.cardname);
578                 return err;
579         } else {
580                 err = hint_list_add(&list, NULL, NULL);
581                 if (err < 0)
582                         goto __error;
583                 *hints = (void **)list.list;
584                 if (list.cardname)
585                         free(list.cardname);
586         }
587         return 0;
588 }
589
590 /**
591  * \brief Free a string list with device name hints.
592  * \param hints A string list to free
593  * \result zero if success, otherwise a negative error code
594  */
595 int snd_device_name_free_hint(void **hints)
596 {
597         char **h;
598
599         if (hints == NULL)
600                 return 0;
601         h = (char **)hints;
602         while (*h) {
603                 free(*h);
604                 h++;
605         }
606         free(hints);
607         return 0;
608 }
609
610 /**
611  * \brief Get a hint Free a string list with device name hints.
612  * \param hint A pointer to hint
613  * \param id Hint ID (see bellow)
614  * \result an allocated ASCII string if success, otherwise NULL
615  *
616  * List of valid IDs:
617  * NAME - name of device
618  * DESC - description of device
619  * IOID - input / output identification (Input or Output strings),
620  *        not present (NULL) means both
621  */
622 char *snd_device_name_get_hint(const void *hint, const char *id)
623 {
624         const char *hint1 = (const char *)hint, *delim;
625         char *res;
626         unsigned size;
627
628         if (strlen(id) != 4)
629                 return NULL;
630         while (*hint1 != '\0') {
631                 delim = strchr(hint1, '|');
632                 if (memcmp(id, hint1, 4) != 0) {
633                         if (delim == NULL)
634                                 return NULL;
635                         hint1 = delim + 1;
636                         continue;
637                 } 
638                 if (delim == NULL)
639                         return strdup(hint1 + 4);
640                 size = delim - hint1 - 4;
641                 res = malloc(size + 1);
642                 if (res != NULL) {
643                         memcpy(res, hint1 + 4, size);
644                         res[size] = '\0';
645                 }
646                 return res;
647         }
648         return NULL;
649 }