]> code.delx.au - pulseaudio/blob - src/modules/module-hal-detect.c
* add SVN $Id$ tags
[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
38 #include <pulsecore/core-error.h>
39 #include <pulsecore/module.h>
40 #include <pulsecore/log.h>
41 #include <pulsecore/core-subscribe.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 static const char*const capabilities[] = { "alsa", "oss" };
56
57 typedef enum {
58 CAP_ALSA,
59 CAP_OSS,
60 CAP_MAX
61 } capability_t;
62
63 typedef enum {
64 ALSA_TYPE_SINK,
65 ALSA_TYPE_SOURCE,
66 ALSA_TYPE_OTHER,
67 ALSA_TYPE_MAX
68 } alsa_type_t;
69
70 struct device {
71 char *udi;
72 pa_module *module;
73 };
74
75 struct userdata {
76 pa_core *core;
77 pa_subscription *sub;
78 LibHalContext *ctx;
79 capability_t capability;
80 pa_dbus_connection *conn;
81 pa_hashmap *by_udi;
82 pa_hashmap *by_module;
83 };
84
85 struct timerdata {
86 struct userdata *u;
87 char *udi;
88 };
89
90 static alsa_type_t hal_device_get_alsa_type(LibHalContext *ctx, const char *udi,
91 DBusError *error)
92 {
93 char *type;
94 alsa_type_t t;
95
96 type = libhal_device_get_property_string(ctx, udi, "alsa.type", error);
97 if (!type || dbus_error_is_set(error))
98 return FALSE;
99
100 if (!strcmp(type, "playback")) {
101 t = ALSA_TYPE_SINK;
102 } else if (!strcmp(type, "capture")) {
103 t = ALSA_TYPE_SOURCE;
104 } else {
105 t = ALSA_TYPE_OTHER;
106 }
107 libhal_free_string(type);
108
109 return t;
110 }
111
112 static int hal_device_get_alsa_card(LibHalContext *ctx, const char *udi,
113 DBusError *error)
114 {
115 return libhal_device_get_property_int(ctx, udi, "alsa.card", error);
116 }
117
118 static int hal_device_get_alsa_device(LibHalContext *ctx, const char *udi,
119 DBusError *error)
120 {
121 return libhal_device_get_property_int(ctx, udi, "alsa.device", error);
122 }
123
124 static void hal_device_free(struct device* d) {
125 pa_xfree(d->udi);
126 pa_xfree(d);
127 }
128
129 static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
130 hal_device_free((struct device*) d);
131 }
132
133 static dbus_bool_t hal_device_add_alsa(struct userdata *u, const char *udi,
134 DBusError *error)
135 {
136 char args[64];
137 alsa_type_t type;
138 int device, card;
139 pa_module *m;
140 struct device *d;
141 const char *module_name;
142
143 type = hal_device_get_alsa_type(u->ctx, udi, error);
144 if (dbus_error_is_set(error) || type == ALSA_TYPE_OTHER) {
145 return FALSE;
146 }
147
148 device = hal_device_get_alsa_device(u->ctx, udi, error);
149 if (dbus_error_is_set(error) || device != 0)
150 return FALSE;
151
152 card = hal_device_get_alsa_card(u->ctx, udi, error);
153 if (dbus_error_is_set(error))
154 return FALSE;
155
156 module_name = (type == ALSA_TYPE_SINK) ? "module-alsa-sink"
157 : "module-alsa-source";
158 snprintf(args, sizeof(args), "device=hw:%u", card);
159 if (!(m = pa_module_load(u->core, module_name, args)))
160 return FALSE;
161
162 d = pa_xmalloc(sizeof(struct device));
163 d->udi = pa_xstrdup(udi);
164 d->module = m;
165
166 pa_hashmap_put(u->by_module, m, d);
167 pa_hashmap_put(u->by_udi, udi, d);
168
169 return TRUE;
170 }
171
172 static int hal_device_add_all(struct userdata *u, capability_t capability)
173 {
174 DBusError error;
175 int i,n,count;
176 dbus_bool_t r;
177 char** udis;
178 const char* cap = capabilities[capability];
179
180 assert(capability < CAP_MAX);
181
182 pa_log_info(__FILE__": Trying capability %u (%s)", capability, cap);
183 dbus_error_init(&error);
184 udis = libhal_find_device_by_capability(u->ctx, cap, &n, &error);
185 if (dbus_error_is_set(&error)) {
186 pa_log_error(__FILE__": Error finding devices: %s: %s", error.name,
187 error.message);
188 dbus_error_free(&error);
189 return -1;
190 }
191 count = 0;
192 for (i = 0; i < n; ++i) {
193 switch(capability) {
194 case CAP_ALSA:
195 r = hal_device_add_alsa(u, udis[i], &error);
196 break;
197 case CAP_OSS:
198 /* r = hal_device_add_oss(u, udis[i], &error)
199 * break; */
200 case CAP_MAX:
201 default:
202 assert(FALSE);
203 break;
204 }
205
206 if (dbus_error_is_set(&error)) {
207 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
208 error.message);
209 dbus_error_free(&error);
210 count = -1;
211 break;
212 }
213 if (r)
214 ++count;
215 }
216
217 libhal_free_string_array(udis);
218 u->capability = capability;
219 return count;
220 }
221
222 static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi,
223 const char* cap, DBusError *error)
224 {
225 dbus_bool_t has_prop;
226 has_prop = libhal_device_property_exists(ctx, udi, "info.capabilities",
227 error);
228 if (!has_prop || dbus_error_is_set(error))
229 return FALSE;
230
231 return libhal_device_query_capability(ctx, udi, cap, error);
232 }
233
234 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev,
235 const struct timeval *tv, void *userdata)
236 {
237 DBusError error;
238 struct timerdata *td = (struct timerdata*) userdata;
239
240 dbus_error_init(&error);
241 if (!libhal_device_exists(td->u->ctx, td->udi, &error))
242 goto exit;
243
244 switch(td->u->capability) {
245 case CAP_ALSA:
246 hal_device_add_alsa(td->u, td->udi, &error);
247 break;
248 case CAP_OSS:
249 /* hal_device_add_oss(td->u, td->udi, &error);
250 * break; */
251 case CAP_MAX:
252 default:
253 /* not reached */
254 assert(FALSE);
255 break;
256 }
257
258 exit:
259 if (dbus_error_is_set(&error)) {
260 pa_log_error(__FILE__": Error adding device: %s: %s", error.name,
261 error.message);
262 dbus_error_free(&error);
263 }
264
265 pa_xfree(td->udi);
266 pa_xfree(td);
267 ea->time_free(ev);
268 }
269
270 static void device_added_cb(LibHalContext *ctx, const char *udi)
271 {
272 DBusError error;
273 struct timeval tv;
274 dbus_bool_t has_cap;
275 struct timerdata *t;
276 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
277 const char* cap = capabilities[u->capability];
278
279 pa_log_debug(__FILE__": HAL Device added: %s", udi);
280
281 dbus_error_init(&error);
282 has_cap = device_has_capability(ctx, udi, cap, &error);
283 if (dbus_error_is_set(&error)) {
284 pa_log_error(__FILE__": Error getting capability: %s: %s", error.name,
285 error.message);
286 dbus_error_free(&error);
287 return;
288 }
289
290 /* skip it */
291 if (!has_cap)
292 return;
293
294 /* actually add the device one second later */
295 t = pa_xmalloc(sizeof(struct timerdata));
296 t->u = u;
297 t->udi = pa_xstrdup(udi);
298
299 gettimeofday(&tv, NULL);
300 tv.tv_sec += 1;
301 u->core->mainloop->time_new(u->core->mainloop, &tv,
302 device_added_time_cb, t);
303 }
304
305 static void device_removed_cb(LibHalContext* ctx, const char *udi)
306 {
307 struct device *d;
308 struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
309
310 pa_log_debug(__FILE__": Device removed: %s", udi);
311 if ((d = pa_hashmap_remove(u->by_udi, udi))) {
312 d = pa_hashmap_remove(u->by_module, d->module);
313 pa_log_debug(__FILE__": Unloading: %s <%s>", d->module->name, d->module->argument);
314 pa_module_unload_request(d->module);
315 hal_device_free(d);
316 }
317 }
318
319 #if 0
320 static void new_capability_cb(LibHalContext *ctx, const char *udi,
321 const char* capability)
322 {
323 }
324
325 static void lost_capability_cb(LibHalContext *ctx, const char *udi,
326 const char* capability)
327 {
328 }
329
330 static void property_modified_cb(LibHalContext *ctx, const char *udi,
331 const char* key,
332 dbus_bool_t is_removed,
333 dbus_bool_t is_added)
334 {
335 }
336 #endif
337
338 static void subscribe_notify_cb(pa_core *c, pa_subscription_event_type_t type,
339 uint32_t idx, void *userdata)
340 {
341 pa_module *m;
342 struct device *d;
343 struct userdata *u = (struct userdata*) userdata;
344
345 /* only listen for module remove events */
346 if (type != (PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE))
347 return;
348
349 if (!(m = pa_idxset_get_by_index(c->modules, idx)))
350 return;
351
352 /* we found the module, see if it's one we care about */
353 if ((d = pa_hashmap_remove(u->by_module, m))) {
354 pa_log_debug(__FILE__": Removing module #%u %s: %s",
355 m->index, m->name, d->udi);
356 d = pa_hashmap_remove(u->by_udi, d->udi);
357 hal_device_free(d);
358 }
359 }
360
361
362 static void pa_hal_context_free(LibHalContext* hal_ctx)
363 {
364 DBusError error;
365
366 dbus_error_init(&error);
367 libhal_ctx_shutdown(hal_ctx, &error);
368 libhal_ctx_free(hal_ctx);
369
370 if (dbus_error_is_set(&error)) {
371 dbus_error_free(&error);
372 }
373 }
374
375 static void userdata_free(struct userdata *u) {
376 pa_hal_context_free(u->ctx);
377 pa_subscription_free(u->sub);
378 /* free the hashmap */
379 pa_hashmap_free(u->by_module, NULL, NULL);
380 /* free the devices with the hashmap */
381 pa_hashmap_free(u->by_udi, hal_device_free_cb, NULL);
382 pa_dbus_connection_unref(u->conn);
383 pa_xfree(u);
384 }
385
386 static LibHalContext* pa_hal_context_new(pa_core* c, DBusConnection *conn)
387 {
388 DBusError error;
389 LibHalContext *hal_ctx = NULL;
390
391 dbus_error_init(&error);
392 if (!(hal_ctx = libhal_ctx_new())) {
393 pa_log_error(__FILE__": libhal_ctx_new() failed");
394 goto fail;
395 }
396
397 if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) {
398 pa_log_error(__FILE__": Error establishing DBUS connection: %s: %s",
399 error.name, error.message);
400 goto fail;
401 }
402
403 if (!libhal_ctx_init(hal_ctx, &error)) {
404 pa_log_error(__FILE__": Couldn't connect to hald: %s: %s",
405 error.name, error.message);
406 goto fail;
407 }
408
409 return hal_ctx;
410
411 fail:
412 if (hal_ctx)
413 pa_hal_context_free(hal_ctx);
414
415 if (dbus_error_is_set(&error))
416 dbus_error_free(&error);
417
418 return NULL;
419 }
420
421 int pa__init(pa_core *c, pa_module*m) {
422 int n;
423 DBusError error;
424 pa_dbus_connection *conn;
425 struct userdata *u = NULL;
426 LibHalContext *hal_ctx = NULL;
427
428 assert(c);
429 assert(m);
430
431 dbus_error_init(&error);
432 if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) {
433 pa_log_error(__FILE__": Unable to contact DBUS system bus: %s: %s",
434 error.name, error.message);
435 dbus_error_free(&error);
436 return -1;
437 }
438
439 if (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) {
440 /* pa_hal_context_new() logs appropriate errors */
441 return -1;
442 }
443
444 u = pa_xmalloc(sizeof(struct userdata));
445 u->core = c;
446 u->ctx = hal_ctx;
447 u->conn = conn;
448 u->by_module = pa_hashmap_new(NULL, NULL);
449 u->by_udi = pa_hashmap_new(pa_idxset_string_hash_func,
450 pa_idxset_string_compare_func);
451 u->sub = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_MODULE,
452 subscribe_notify_cb, (void*) u);
453 m->userdata = (void*) u;
454
455 #if HAVE_ALSA
456 if ((n = hal_device_add_all(u, CAP_ALSA)) <= 0)
457 #endif
458 #if HAVE_OSS
459 if ((n = hal_device_add_all(u, CAP_OSS)) <= 0)
460 #endif
461 {
462 pa_log_warn(__FILE__": failed to detect any sound hardware.");
463 userdata_free(u);
464 return -1;
465 }
466
467 libhal_ctx_set_user_data(hal_ctx, (void*) u);
468 libhal_ctx_set_device_added(hal_ctx, device_added_cb);
469 libhal_ctx_set_device_removed(hal_ctx, device_removed_cb);
470
471 dbus_error_init(&error);
472 if (!libhal_device_property_watch_all(hal_ctx, &error)) {
473 pa_log_error(__FILE__": error monitoring device list: %s: %s",
474 error.name, error.message);
475 dbus_error_free(&error);
476 userdata_free(u);
477 return -1;
478 }
479
480 /*
481 libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb);
482 libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb);
483 libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);
484 */
485
486 pa_log_info(__FILE__": loaded %i modules.", n);
487
488 return 0;
489 }
490
491
492 void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
493 assert (c && m);
494
495 /* free the user data */
496 userdata_free(m->userdata);
497 }