]> code.delx.au - pulseaudio/blob - src/modules/module-hal-detect.c
* only load an OSS driver for the first device of a sound card, similar to what is...
[pulseaudio] / src / modules / module-hal-detect.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <assert.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35
36 #include <pulse/xmalloc.h>
37 #include <pulse/timeval.h>
38
39 #include <pulsecore/core-error.h>
40 #include <pulsecore/module.h>
41 #include <pulsecore/log.h>
42 #include <pulsecore/hashmap.h>
43 #include <pulsecore/idxset.h>
44 #include <pulsecore/core-util.h>
45
46 #include <hal/libhal.h>
47
48 #include "dbus-util.h"
49 #include "module-hal-detect-symdef.h"
50
51 PA_MODULE_AUTHOR("Shahms King")
52 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
53 PA_MODULE_VERSION(PACKAGE_VERSION)
54
55 typedef enum {
56 #ifdef HAVE_ALSA
57 CAP_ALSA,
58 #endif
59 #ifdef HAVE_OSS
60 CAP_OSS,
61 #endif
62 CAP_MAX
63 } capability_t;
64
65 static const char* const capabilities[CAP_MAX] = {
66 #ifdef HAVE_ALSA
67 [CAP_ALSA] = "alsa",
68 #endif
69 #ifdef HAVE_OSS
70 [CAP_OSS] = "oss",
71 #endif
72 };
73
74 struct device {
75 uint32_t index;
76 char *udi;
77 };
78
79 struct userdata {
80 pa_core *core;
81 LibHalContext *ctx;
82 capability_t capability;
83 pa_dbus_connection *conn;
84 pa_hashmap *devices;
85 };
86
87 struct timerdata {
88 struct userdata *u;
89 char *udi;
90 };
91
92 static const char* get_capability_name(capability_t cap) {
93 if (cap >= CAP_MAX)
94 return NULL;
95 return capabilities[cap];
96 }
97
98 static void hal_device_free(struct device* d) {
99 pa_xfree(d->udi);
100 pa_xfree(d);
101 }
102
103 static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
104 hal_device_free((struct device*) d);
105 }
106
107 static const char *strip_udi(const char *udi) {
108 const char *slash;
109 if ((slash = strrchr(udi, '/')))
110 return slash+1;
111
112 return udi;
113 }
114
115 #ifdef HAVE_ALSA
116 typedef enum {
117 ALSA_TYPE_SINK,
118 ALSA_TYPE_SOURCE,
119 ALSA_TYPE_OTHER,
120 ALSA_TYPE_MAX
121 } alsa_type_t;
122
123 static alsa_type_t hal_device_get_alsa_type(LibHalContext *ctx, const char *udi,
124 DBusError *error)
125 {
126 char *type;
127 alsa_type_t t;
128
129 type = libhal_device_get_property_string(ctx, udi, "alsa.type", error);
130 if (!type || dbus_error_is_set(error))
131 return FALSE;
132
133 if (!strcmp(type, "playback")) {
134 t = ALSA_TYPE_SINK;
135 } else if (!strcmp(type, "capture")) {
136 t = ALSA_TYPE_SOURCE;
137 } else {
138 t = ALSA_TYPE_OTHER;
139 }
140 libhal_free_string(type);
141
142 return t;
143 }
144
145 static int hal_device_get_alsa_card(LibHalContext *ctx, const char *udi,
146 DBusError *error)
147 {
148 return libhal_device_get_property_int(ctx, udi, "alsa.card", error);
149 }
150
151 static int hal_device_get_alsa_device(LibHalContext *ctx, const char *udi,
152 DBusError *error)
153 {
154 return libhal_device_get_property_int(ctx, udi, "alsa.device", error);
155 }
156
157 static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi,
158 DBusError *error)
159 {
160 char args[128];
161 alsa_type_t type;
162 int device, card;
163 const char *module_name;
164
165 type = hal_device_get_alsa_type(u->ctx, udi, error);
166 if (dbus_error_is_set(error) || type == ALSA_TYPE_OTHER)
167 return NULL;
168
169 device = hal_device_get_alsa_device(u->ctx, udi, error);
170 if (dbus_error_is_set(error) || device != 0)
171 return NULL;
172
173 card = hal_device_get_alsa_card(u->ctx, udi, error);
174 if (dbus_error_is_set(error))
175 return NULL;
176
177 if (type == ALSA_TYPE_SINK) {
178 module_name = "module-alsa-sink";
179 snprintf(args, sizeof(args), "device=hw:%u sink_name=alsa_output.%s", card, strip_udi(udi));
180 } else {
181 module_name = "module-alsa-source";
182 snprintf(args, sizeof(args), "device=hw:%u source_name=alsa_input.%s", card, strip_udi(udi));
183 }
184
185 return pa_module_load(u->core, module_name, args);
186 }
187
188 #endif
189
190 #ifdef HAVE_OSS
191 static dbus_bool_t hal_device_is_oss_pcm(LibHalContext *ctx, const char *udi,
192 DBusError *error)
193 {
194 dbus_bool_t rv = FALSE;
195 char* type, *device_file = NULL;
196 int device;
197
198 type = libhal_device_get_property_string(ctx, udi, "oss.type", error);
199 if (!type || dbus_error_is_set(error))
200 return FALSE;
201
202 if (!strcmp(type, "pcm")) {
203 char *e;
204
205 device = libhal_device_get_property_int(ctx, udi, "oss.device", error);
206 if (dbus_error_is_set(error) || device != 0)
207 goto exit;
208
209 device_file = libhal_device_get_property_string(ctx, udi, "oss.device_file",
210 error);
211 if (!device_file || dbus_error_is_set(error))
212 goto exit;
213
214 /* hack to ignore /dev/audio style devices */
215 if ((e = strrchr(device_file, '/')))
216 rv = !pa_startswith(e + 1, "audio");
217 }
218
219 exit:
220 libhal_free_string(type);
221 libhal_free_string(device_file);
222 return rv;
223 }
224
225 static pa_module* hal_device_load_oss(struct userdata *u, const char *udi,
226 DBusError *error)
227 {
228 char args[256];
229 char* device;
230
231 if (!hal_device_is_oss_pcm(u->ctx, udi, error) || dbus_error_is_set(error))
232 return NULL;
233
234 device = libhal_device_get_property_string(u->ctx, udi, "oss.device_file",
235 error);
236 if (!device || dbus_error_is_set(error))
237 return NULL;
238
239 snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi));
240 libhal_free_string(device);
241
242 return pa_module_load(u->core, "module-oss", args);
243 }
244 #endif
245
246 static dbus_bool_t hal_device_add(struct userdata *u, const char *udi,
247 DBusError *error)
248 {
249 pa_module* m;
250 struct device *d;
251
252 switch(u->capability) {
253 #ifdef HAVE_ALSA
254 case CAP_ALSA:
255 m = hal_device_load_alsa(u, udi, error);
256 break;
257 #endif
258 #ifdef HAVE_OSS
259 case CAP_OSS:
260 m = hal_device_load_oss(u, udi, error);
261 break;
262 #endif
263 default:
264 assert(FALSE); /* never reached */
265 break;
266 }
267
268 if (!m || dbus_error_is_set(error))
269 return FALSE;
270
271 d = pa_xnew(struct device, 1);
272 d->udi = pa_xstrdup(udi);
273 d->index = m->index;
274
275 pa_hashmap_put(u->devices, d->udi, d);
276
277 return TRUE;
278 }
279
280 static int hal_device_add_all(struct userdata *u, capability_t capability)
281 {
282 DBusError error;
283 int i,n,count;
284 dbus_bool_t r;
285 char** udis;
286 const char* cap = get_capability_name(capability);
287
288 assert(capability < CAP_MAX);
289
290 pa_log_info(__FILE__": Trying capability %u (%s)", capability, cap);
291 dbus_error_init(&error);
292 udis = libhal_find_device_by_capability(u->ctx, cap, &n, &error);
293 if (dbus_error_is_set(&error)) {
294 pa_log_error(__FILE__": Error finding devices: %s: %s", error.name,
295 error.message);
296 dbus_error_free(&error);
297 return -1;
298 }
299 count = 0;
300 u->capability = capability;
301 for (i = 0; i < n; ++i) {
302 r = hal_device_add(u, udis[i], &error);
303 if (dbus_error_is_set(&error)) {
304 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
305 error.message);
306 dbus_error_free(&error);
307 count = -1;
308 break;
309 }
310 if (r)
311 ++count;
312 }
313
314 libhal_free_string_array(udis);
315 return count;
316 }
317
318 static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi,
319 const char* cap, DBusError *error)
320 {
321 dbus_bool_t has_prop;
322 has_prop = libhal_device_property_exists(ctx, udi, "info.capabilities",
323 error);
324 if (!has_prop || dbus_error_is_set(error))
325 return FALSE;
326
327 return libhal_device_query_capability(ctx, udi, cap, error);
328 }
329
330 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev,
331 const struct timeval *tv, void *userdata)
332 {
333 DBusError error;
334 struct timerdata *td = (struct timerdata*) userdata;
335
336 dbus_error_init(&error);
337 if (libhal_device_exists(td->u->ctx, td->udi, &error))
338 hal_device_add(td->u, td->udi, &error);
339
340 if (dbus_error_is_set(&error)) {
341 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
342 error.message);
343 dbus_error_free(&error);
344 }
345
346 pa_xfree(td->udi);
347 pa_xfree(td);
348 ea->time_free(ev);
349 }
350
351 static void device_added_cb(LibHalContext *ctx, const char *udi)
352 {
353 DBusError error;
354 struct timeval tv;
355 dbus_bool_t has_cap;
356 struct timerdata *t;
357 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
358 const char* cap = get_capability_name(u->capability);
359
360 pa_log_debug(__FILE__": HAL Device added: %s", udi);
361
362 dbus_error_init(&error);
363 has_cap = device_has_capability(ctx, udi, cap, &error);
364 if (dbus_error_is_set(&error)) {
365 pa_log_error(__FILE__": Error getting capability: %s: %s", error.name,
366 error.message);
367 dbus_error_free(&error);
368 return;
369 }
370
371 /* skip it */
372 if (!has_cap)
373 return;
374
375 /* actually add the device 1/2 second later */
376 t = pa_xnew(struct timerdata, 1);
377 t->u = u;
378 t->udi = pa_xstrdup(udi);
379
380 pa_gettimeofday(&tv);
381 pa_timeval_add(&tv, 500000);
382 u->core->mainloop->time_new(u->core->mainloop, &tv,
383 device_added_time_cb, t);
384 }
385
386 static void device_removed_cb(LibHalContext* ctx, const char *udi)
387 {
388 struct device *d;
389 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
390
391 pa_log_debug(__FILE__": Device removed: %s", udi);
392 if ((d = pa_hashmap_remove(u->devices, udi))) {
393 pa_module_unload_by_index(u->core, d->index);
394 hal_device_free(d);
395 }
396 }
397
398 static void new_capability_cb(LibHalContext *ctx, const char *udi,
399 const char* capability)
400 {
401 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
402 const char* capname = get_capability_name(u->capability);
403
404 if (capname && !strcmp(capname, capability)) {
405 /* capability we care about, pretend it's a new device */
406 device_added_cb(ctx, udi);
407 }
408 }
409
410 static void lost_capability_cb(LibHalContext *ctx, const char *udi,
411 const char* capability)
412 {
413 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
414 const char* capname = get_capability_name(u->capability);
415
416 if (capname && !strcmp(capname, capability)) {
417 /* capability we care about, pretend it was removed */
418 device_removed_cb(ctx, udi);
419 }
420 }
421
422 #if 0
423 static void property_modified_cb(LibHalContext *ctx, const char *udi,
424 const char* key,
425 dbus_bool_t is_removed,
426 dbus_bool_t is_added)
427 {
428 }
429 #endif
430
431 static void pa_hal_context_free(LibHalContext* hal_ctx)
432 {
433 DBusError error;
434
435 dbus_error_init(&error);
436 libhal_ctx_shutdown(hal_ctx, &error);
437 libhal_ctx_free(hal_ctx);
438
439 if (dbus_error_is_set(&error)) {
440 dbus_error_free(&error);
441 }
442 }
443
444 static void userdata_free(struct userdata *u) {
445 pa_hal_context_free(u->ctx);
446 /* free the devices with the hashmap */
447 pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
448 pa_dbus_connection_unref(u->conn);
449 pa_xfree(u);
450 }
451
452 static LibHalContext* pa_hal_context_new(pa_core* c, DBusConnection *conn)
453 {
454 DBusError error;
455 LibHalContext *hal_ctx = NULL;
456
457 dbus_error_init(&error);
458 if (!(hal_ctx = libhal_ctx_new())) {
459 pa_log_error(__FILE__": libhal_ctx_new() failed");
460 goto fail;
461 }
462
463 if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) {
464 pa_log_error(__FILE__": Error establishing DBUS connection: %s: %s",
465 error.name, error.message);
466 goto fail;
467 }
468
469 if (!libhal_ctx_init(hal_ctx, &error)) {
470 pa_log_error(__FILE__": Couldn't connect to hald: %s: %s",
471 error.name, error.message);
472 goto fail;
473 }
474
475 return hal_ctx;
476
477 fail:
478 if (hal_ctx)
479 pa_hal_context_free(hal_ctx);
480
481 if (dbus_error_is_set(&error))
482 dbus_error_free(&error);
483
484 return NULL;
485 }
486
487 int pa__init(pa_core *c, pa_module*m) {
488 int n;
489 DBusError error;
490 pa_dbus_connection *conn;
491 struct userdata *u = NULL;
492 LibHalContext *hal_ctx = NULL;
493
494 assert(c);
495 assert(m);
496
497 dbus_error_init(&error);
498 if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) {
499 pa_log_error(__FILE__": Unable to contact DBUS system bus: %s: %s",
500 error.name, error.message);
501 dbus_error_free(&error);
502 return -1;
503 }
504
505 if (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) {
506 /* pa_hal_context_new() logs appropriate errors */
507 return -1;
508 }
509
510 u = pa_xnew(struct userdata, 1);
511 u->core = c;
512 u->ctx = hal_ctx;
513 u->conn = conn;
514 u->devices = pa_hashmap_new(pa_idxset_string_hash_func,
515 pa_idxset_string_compare_func);
516 m->userdata = (void*) u;
517
518 #ifdef HAVE_ALSA
519 if ((n = hal_device_add_all(u, CAP_ALSA)) <= 0)
520 #endif
521 #ifdef HAVE_OSS
522 if ((n = hal_device_add_all(u, CAP_OSS)) <= 0)
523 #endif
524 {
525 pa_log_warn(__FILE__": failed to detect any sound hardware.");
526 userdata_free(u);
527 return -1;
528 }
529
530 libhal_ctx_set_user_data(hal_ctx, (void*) u);
531 libhal_ctx_set_device_added(hal_ctx, device_added_cb);
532 libhal_ctx_set_device_removed(hal_ctx, device_removed_cb);
533 libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb);
534 libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb);
535 /*libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);*/
536
537 dbus_error_init(&error);
538 if (!libhal_device_property_watch_all(hal_ctx, &error)) {
539 pa_log_error(__FILE__": error monitoring device list: %s: %s",
540 error.name, error.message);
541 dbus_error_free(&error);
542 userdata_free(u);
543 return -1;
544 }
545
546 pa_log_info(__FILE__": loaded %i modules.", n);
547
548 return 0;
549 }
550
551
552 void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
553 assert (c && m);
554
555 /* free the user data */
556 userdata_free(m->userdata);
557 }