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