]> code.delx.au - pulseaudio/blob - src/modules/module-hal-detect.c
increase module argument buffer size to prevent truncating names
[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* device;
196 char* type;
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 device = libhal_device_get_property_string(ctx, udi, "oss.device_file",
204 error);
205 if (!device || dbus_error_is_set(error))
206 goto exit;
207
208 /* hack to ignore /dev/audio style devices */
209 if ((device = strrchr(device, '/')))
210 rv = (pa_startswith(device + 1, "audio")) ? FALSE : TRUE;
211 }
212
213 exit:
214 libhal_free_string(type);
215 return rv;
216 }
217
218 static pa_module* hal_device_load_oss(struct userdata *u, const char *udi,
219 DBusError *error)
220 {
221 char args[256];
222 char* device;
223
224 if (!hal_device_is_oss_pcm(u->ctx, udi, error) || dbus_error_is_set(error))
225 return NULL;
226
227 device = libhal_device_get_property_string(u->ctx, udi, "oss.device_file",
228 error);
229 if (!device || dbus_error_is_set(error))
230 return NULL;
231
232 snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi));
233 libhal_free_string(device);
234
235 return pa_module_load(u->core, "module-oss", args);
236 }
237 #endif
238
239 static dbus_bool_t hal_device_add(struct userdata *u, const char *udi,
240 DBusError *error)
241 {
242 pa_module* m;
243 struct device *d;
244
245 switch(u->capability) {
246 #ifdef HAVE_ALSA
247 case CAP_ALSA:
248 m = hal_device_load_alsa(u, udi, error);
249 break;
250 #endif
251 #ifdef HAVE_OSS
252 case CAP_OSS:
253 m = hal_device_load_oss(u, udi, error);
254 break;
255 #endif
256 default:
257 assert(FALSE); /* never reached */
258 break;
259 }
260
261 if (!m || dbus_error_is_set(error))
262 return FALSE;
263
264 d = pa_xnew(struct device, 1);
265 d->udi = pa_xstrdup(udi);
266 d->index = m->index;
267
268 pa_hashmap_put(u->devices, d->udi, d);
269
270 return TRUE;
271 }
272
273 static int hal_device_add_all(struct userdata *u, capability_t capability)
274 {
275 DBusError error;
276 int i,n,count;
277 dbus_bool_t r;
278 char** udis;
279 const char* cap = get_capability_name(capability);
280
281 assert(capability < CAP_MAX);
282
283 pa_log_info(__FILE__": Trying capability %u (%s)", capability, cap);
284 dbus_error_init(&error);
285 udis = libhal_find_device_by_capability(u->ctx, cap, &n, &error);
286 if (dbus_error_is_set(&error)) {
287 pa_log_error(__FILE__": Error finding devices: %s: %s", error.name,
288 error.message);
289 dbus_error_free(&error);
290 return -1;
291 }
292 count = 0;
293 u->capability = capability;
294 for (i = 0; i < n; ++i) {
295 r = hal_device_add(u, udis[i], &error);
296 if (dbus_error_is_set(&error)) {
297 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
298 error.message);
299 dbus_error_free(&error);
300 count = -1;
301 break;
302 }
303 if (r)
304 ++count;
305 }
306
307 libhal_free_string_array(udis);
308 return count;
309 }
310
311 static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi,
312 const char* cap, DBusError *error)
313 {
314 dbus_bool_t has_prop;
315 has_prop = libhal_device_property_exists(ctx, udi, "info.capabilities",
316 error);
317 if (!has_prop || dbus_error_is_set(error))
318 return FALSE;
319
320 return libhal_device_query_capability(ctx, udi, cap, error);
321 }
322
323 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev,
324 const struct timeval *tv, void *userdata)
325 {
326 DBusError error;
327 struct timerdata *td = (struct timerdata*) userdata;
328
329 dbus_error_init(&error);
330 if (libhal_device_exists(td->u->ctx, td->udi, &error))
331 hal_device_add(td->u, td->udi, &error);
332
333 if (dbus_error_is_set(&error)) {
334 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
335 error.message);
336 dbus_error_free(&error);
337 }
338
339 pa_xfree(td->udi);
340 pa_xfree(td);
341 ea->time_free(ev);
342 }
343
344 static void device_added_cb(LibHalContext *ctx, const char *udi)
345 {
346 DBusError error;
347 struct timeval tv;
348 dbus_bool_t has_cap;
349 struct timerdata *t;
350 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
351 const char* cap = get_capability_name(u->capability);
352
353 pa_log_debug(__FILE__": HAL Device added: %s", udi);
354
355 dbus_error_init(&error);
356 has_cap = device_has_capability(ctx, udi, cap, &error);
357 if (dbus_error_is_set(&error)) {
358 pa_log_error(__FILE__": Error getting capability: %s: %s", error.name,
359 error.message);
360 dbus_error_free(&error);
361 return;
362 }
363
364 /* skip it */
365 if (!has_cap)
366 return;
367
368 /* actually add the device 1/2 second later */
369 t = pa_xnew(struct timerdata, 1);
370 t->u = u;
371 t->udi = pa_xstrdup(udi);
372
373 pa_gettimeofday(&tv);
374 pa_timeval_add(&tv, 500000);
375 u->core->mainloop->time_new(u->core->mainloop, &tv,
376 device_added_time_cb, t);
377 }
378
379 static void device_removed_cb(LibHalContext* ctx, const char *udi)
380 {
381 struct device *d;
382 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
383
384 pa_log_debug(__FILE__": Device removed: %s", udi);
385 if ((d = pa_hashmap_remove(u->devices, udi))) {
386 pa_module_unload_by_index(u->core, d->index);
387 hal_device_free(d);
388 }
389 }
390
391 static void new_capability_cb(LibHalContext *ctx, const char *udi,
392 const char* capability)
393 {
394 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
395 const char* capname = get_capability_name(u->capability);
396
397 if (capname && !strcmp(capname, capability)) {
398 /* capability we care about, pretend it's a new device */
399 device_added_cb(ctx, udi);
400 }
401 }
402
403 static void lost_capability_cb(LibHalContext *ctx, const char *udi,
404 const char* capability)
405 {
406 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
407 const char* capname = get_capability_name(u->capability);
408
409 if (capname && !strcmp(capname, capability)) {
410 /* capability we care about, pretend it was removed */
411 device_removed_cb(ctx, udi);
412 }
413 }
414
415 #if 0
416 static void property_modified_cb(LibHalContext *ctx, const char *udi,
417 const char* key,
418 dbus_bool_t is_removed,
419 dbus_bool_t is_added)
420 {
421 }
422 #endif
423
424 static void pa_hal_context_free(LibHalContext* hal_ctx)
425 {
426 DBusError error;
427
428 dbus_error_init(&error);
429 libhal_ctx_shutdown(hal_ctx, &error);
430 libhal_ctx_free(hal_ctx);
431
432 if (dbus_error_is_set(&error)) {
433 dbus_error_free(&error);
434 }
435 }
436
437 static void userdata_free(struct userdata *u) {
438 pa_hal_context_free(u->ctx);
439 /* free the devices with the hashmap */
440 pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
441 pa_dbus_connection_unref(u->conn);
442 pa_xfree(u);
443 }
444
445 static LibHalContext* pa_hal_context_new(pa_core* c, DBusConnection *conn)
446 {
447 DBusError error;
448 LibHalContext *hal_ctx = NULL;
449
450 dbus_error_init(&error);
451 if (!(hal_ctx = libhal_ctx_new())) {
452 pa_log_error(__FILE__": libhal_ctx_new() failed");
453 goto fail;
454 }
455
456 if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) {
457 pa_log_error(__FILE__": Error establishing DBUS connection: %s: %s",
458 error.name, error.message);
459 goto fail;
460 }
461
462 if (!libhal_ctx_init(hal_ctx, &error)) {
463 pa_log_error(__FILE__": Couldn't connect to hald: %s: %s",
464 error.name, error.message);
465 goto fail;
466 }
467
468 return hal_ctx;
469
470 fail:
471 if (hal_ctx)
472 pa_hal_context_free(hal_ctx);
473
474 if (dbus_error_is_set(&error))
475 dbus_error_free(&error);
476
477 return NULL;
478 }
479
480 int pa__init(pa_core *c, pa_module*m) {
481 int n;
482 DBusError error;
483 pa_dbus_connection *conn;
484 struct userdata *u = NULL;
485 LibHalContext *hal_ctx = NULL;
486
487 assert(c);
488 assert(m);
489
490 dbus_error_init(&error);
491 if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) {
492 pa_log_error(__FILE__": Unable to contact DBUS system bus: %s: %s",
493 error.name, error.message);
494 dbus_error_free(&error);
495 return -1;
496 }
497
498 if (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) {
499 /* pa_hal_context_new() logs appropriate errors */
500 return -1;
501 }
502
503 u = pa_xnew(struct userdata, 1);
504 u->core = c;
505 u->ctx = hal_ctx;
506 u->conn = conn;
507 u->devices = pa_hashmap_new(pa_idxset_string_hash_func,
508 pa_idxset_string_compare_func);
509 m->userdata = (void*) u;
510
511 #ifdef HAVE_ALSA
512 if ((n = hal_device_add_all(u, CAP_ALSA)) <= 0)
513 #endif
514 #ifdef HAVE_OSS
515 if ((n = hal_device_add_all(u, CAP_OSS)) <= 0)
516 #endif
517 {
518 pa_log_warn(__FILE__": failed to detect any sound hardware.");
519 userdata_free(u);
520 return -1;
521 }
522
523 libhal_ctx_set_user_data(hal_ctx, (void*) u);
524 libhal_ctx_set_device_added(hal_ctx, device_added_cb);
525 libhal_ctx_set_device_removed(hal_ctx, device_removed_cb);
526 libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb);
527 libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb);
528 /*libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);*/
529
530 dbus_error_init(&error);
531 if (!libhal_device_property_watch_all(hal_ctx, &error)) {
532 pa_log_error(__FILE__": error monitoring device list: %s: %s",
533 error.name, error.message);
534 dbus_error_free(&error);
535 userdata_free(u);
536 return -1;
537 }
538
539 pa_log_info(__FILE__": loaded %i modules.", n);
540
541 return 0;
542 }
543
544
545 void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
546 assert (c && m);
547
548 /* free the user data */
549 userdata_free(m->userdata);
550 }