1/***
2  This file is part of PulseAudio.
3
4  Copyright 2006 Lennart Poettering
5  Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6  Copyright 2006 Diego Pettenò
7
8  PulseAudio is free software; you can redistribute it and/or modify
9  it under the terms of the GNU Lesser General Public License as published
10  by the Free Software Foundation; either version 2.1 of the License,
11  or (at your option) any later version.
12
13  PulseAudio is distributed in the hope that it will be useful, but
14  WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  General Public License for more details.
17
18  You should have received a copy of the GNU Lesser General Public License
19  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
20***/
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#include <stdio.h>
27#include <unistd.h>
28#include <string.h>
29#include <stdlib.h>
30#include <errno.h>
31#include <stdlib.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34
35#include <pulsecore/core-error.h>
36#include <pulsecore/module.h>
37#include <pulsecore/modargs.h>
38#include <pulsecore/log.h>
39#include <pulsecore/core-util.h>
40#include <pulsecore/macro.h>
41
42PA_MODULE_AUTHOR("Lennart Poettering");
43PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
44PA_MODULE_VERSION(PACKAGE_VERSION);
45PA_MODULE_LOAD_ONCE(true);
46PA_MODULE_USAGE("just-one=<boolean>");
47
48#ifdef __linux__
49PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-detect!");
50#endif
51
52static const char* const valid_modargs[] = {
53    "just-one",
54    NULL
55};
56
57#ifdef HAVE_ALSA
58
59static int detect_alsa(pa_core *c, int just_one) {
60    FILE *f;
61    int n = 0, n_sink = 0, n_source = 0;
62
63    if (!(f = pa_fopen_cloexec("/proc/asound/devices", "r"))) {
64
65        if (errno != ENOENT)
66            pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno));
67
68        return -1;
69    }
70
71    while (!feof(f)) {
72        char line[64], args[64];
73        unsigned device, subdevice;
74        int is_sink;
75        pa_module *m = NULL;
76
77        if (!fgets(line, sizeof(line), f))
78            break;
79
80        line[strcspn(line, "\r\n")] = 0;
81
82        if (pa_endswith(line, "digital audio playback"))
83            is_sink = 1;
84        else if (pa_endswith(line, "digital audio capture"))
85            is_sink = 0;
86        else
87            continue;
88
89        if (just_one && is_sink && n_sink >= 1)
90            continue;
91
92        if (just_one && !is_sink && n_source >= 1)
93            continue;
94
95        if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2)
96            continue;
97
98        /* Only one sink per device */
99        if (subdevice != 0)
100            continue;
101
102        pa_snprintf(args, sizeof(args), "device_id=%u", device);
103        if (pa_module_load(&m, c, is_sink ? "module-alsa-sink" : "module-alsa-source", args) < 0)
104            continue;
105
106        n++;
107
108        if (is_sink)
109            n_sink++;
110        else
111            n_source++;
112    }
113
114    fclose(f);
115
116    return n;
117}
118#endif
119
120#ifdef HAVE_OSS_OUTPUT
121static int detect_oss(pa_core *c, int just_one) {
122    FILE *f;
123    int n = 0, b = 0;
124
125    if (!(f = pa_fopen_cloexec("/dev/sndstat", "r")) &&
126        !(f = pa_fopen_cloexec("/proc/sndstat", "r")) &&
127        !(f = pa_fopen_cloexec("/proc/asound/oss/sndstat", "r"))) {
128
129        if (errno != ENOENT)
130            pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno));
131
132        return -1;
133    }
134
135    while (!feof(f)) {
136        char line[256], args[64];
137        unsigned device;
138        pa_module *m = NULL;
139
140        if (!fgets(line, sizeof(line), f))
141            break;
142
143        line[strcspn(line, "\r\n")] = 0;
144
145        if (!b) {
146            b = pa_streq(line, "Audio devices:") || pa_streq(line, "Installed devices:");
147            continue;
148        }
149
150        if (line[0] == 0)
151            break;
152
153        if (sscanf(line, "%u: ", &device) == 1) {
154            if (device == 0)
155                pa_snprintf(args, sizeof(args), "device=/dev/dsp");
156            else
157                pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
158
159            if (pa_module_load(&m, c, "module-oss", args) < 0)
160                continue;
161
162        } else if (sscanf(line, "pcm%u: ", &device) == 1) {
163            pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
164
165            if (pa_module_load(&m, c, "module-oss", args) < 0)
166                continue;
167
168            if (!pa_endswith(line, "default"))
169                continue;
170
171            const char *p = strrchr(line, '(');
172
173            if (!p)
174                continue;
175
176            if (!c->configured_default_sink && (strstr(p, "play") || (strstr(p, "p:") && !strstr(p, "(0p:")))) {
177                uint32_t idx = PA_IDXSET_INVALID;
178                pa_sink *s;
179                PA_IDXSET_FOREACH(s, c->sinks, idx) {
180                    if (s->module == m) {
181                        pa_core_set_configured_default_sink(c, s->name);
182                        break;
183                    }
184                }
185            }
186
187            if (!c->configured_default_source && (strstr(p, "rec") || (strstr(p, "r:") && !strstr(p, "/0r:")))) {
188                uint32_t idx = PA_IDXSET_INVALID;
189                pa_source *s;
190                PA_IDXSET_FOREACH(s, c->sources, idx) {
191                    if (s->module == m) {
192                        pa_core_set_configured_default_source(c, s->name);
193                        break;
194                    }
195                }
196            }
197        }
198
199        n++;
200
201        if (just_one)
202            break;
203    }
204
205    fclose(f);
206    return n;
207}
208#endif
209
210#ifdef HAVE_SOLARIS
211static int detect_solaris(pa_core *c, int just_one) {
212    struct stat s;
213    const char *dev;
214    char args[64];
215    pa_module *m = NULL;
216
217    dev = getenv("AUDIODEV");
218    if (!dev)
219        dev = "/dev/audio";
220
221    if (stat(dev, &s) < 0) {
222        if (errno != ENOENT)
223            pa_log_error("failed to open device %s: %s", dev, pa_cstrerror(errno));
224        return -1;
225    }
226
227    if (!S_ISCHR(s.st_mode))
228        return 0;
229
230    pa_snprintf(args, sizeof(args), "device=%s", dev);
231
232    if (pa_module_load(&m, c, "module-solaris", args) < 0)
233        return 0;
234
235    return 1;
236}
237#endif
238
239#ifdef OS_IS_WIN32
240static int detect_waveout(pa_core *c, int just_one) {
241    pa_module *m = NULL;
242    /*
243     * FIXME: No point in enumerating devices until the plugin supports
244     * selecting anything but the first.
245     */
246    if (pa_module_load(&m, c, "module-waveout", "") < 0)
247        return 0;
248
249    return 1;
250}
251#endif
252
253int pa__init(pa_module*m) {
254    bool just_one = false;
255    int n = 0;
256    pa_modargs *ma;
257
258    pa_assert(m);
259
260    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
261        pa_log("Failed to parse module arguments");
262        goto fail;
263    }
264
265    if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) {
266        pa_log("just_one= expects a boolean argument.");
267        goto fail;
268    }
269
270#ifdef HAVE_ALSA
271    if ((n = detect_alsa(m->core, just_one)) <= 0)
272#endif
273#ifdef HAVE_OSS_OUTPUT
274    if ((n = detect_oss(m->core, just_one)) <= 0)
275#endif
276#ifdef HAVE_SOLARIS
277    if ((n = detect_solaris(m->core, just_one)) <= 0)
278#endif
279#ifdef OS_IS_WIN32
280    if ((n = detect_waveout(m->core, just_one)) <= 0)
281#endif
282    {
283        pa_log_warn("failed to detect any sound hardware.");
284        goto fail;
285    }
286
287    pa_log_info("loaded %i modules.", n);
288
289    /* We were successful and can unload ourselves now. */
290    pa_module_unload_request(m, true);
291
292    pa_modargs_free(ma);
293
294    return 0;
295
296fail:
297    if (ma)
298        pa_modargs_free(ma);
299
300    return -1;
301}
302