Add copyright notices to all relevant files. (based on svn log)
[profile/ivi/pulseaudio.git] / 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 };
89
90 struct timerdata {
91     struct userdata *u;
92     char *udi;
93 };
94
95 static const char* get_capability_name(capability_t cap) {
96     if (cap >= CAP_MAX)
97         return NULL;
98     return capabilities[cap];
99 }
100
101 static void hal_device_free(struct device* d) {
102     pa_xfree(d->udi);
103     pa_xfree(d);
104 }
105
106 static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
107     hal_device_free((struct device*) d);
108 }
109
110 static const char *strip_udi(const char *udi) {
111     const char *slash;
112     if ((slash = strrchr(udi, '/')))
113         return slash+1;
114
115     return udi;
116 }
117
118 #ifdef HAVE_ALSA
119 typedef enum {
120     ALSA_TYPE_SINK,
121     ALSA_TYPE_SOURCE,
122     ALSA_TYPE_OTHER,
123     ALSA_TYPE_MAX
124 } alsa_type_t;
125
126 static alsa_type_t hal_device_get_alsa_type(LibHalContext *ctx, const char *udi,
127                                             DBusError *error)
128 {
129     char *type;
130     alsa_type_t t;
131
132     type = libhal_device_get_property_string(ctx, udi, "alsa.type", error);
133     if (!type || dbus_error_is_set(error))
134         return FALSE;
135
136     if (!strcmp(type, "playback")) {
137         t = ALSA_TYPE_SINK;
138     } else if (!strcmp(type, "capture")) {
139         t = ALSA_TYPE_SOURCE;
140     } else {
141         t = ALSA_TYPE_OTHER;
142     }
143     libhal_free_string(type);
144
145     return t;
146 }
147
148 static int hal_device_get_alsa_card(LibHalContext *ctx, const char *udi,
149                                     DBusError *error)
150 {
151     return libhal_device_get_property_int(ctx, udi, "alsa.card", error);
152 }
153
154 static int hal_device_get_alsa_device(LibHalContext *ctx, const char *udi,
155                                       DBusError *error)
156 {
157     return libhal_device_get_property_int(ctx, udi, "alsa.device", error);
158 }
159
160 static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi,
161                                        DBusError  *error)
162 {
163     char args[128];
164     alsa_type_t type;
165     int device, card;
166     const char *module_name;
167
168     type = hal_device_get_alsa_type(u->ctx, udi, error);
169     if (dbus_error_is_set(error) || type == ALSA_TYPE_OTHER)
170         return NULL;
171
172     device = hal_device_get_alsa_device(u->ctx, udi, error);
173     if (dbus_error_is_set(error) || device != 0)
174         return NULL;
175
176     card = hal_device_get_alsa_card(u->ctx, udi, error);
177     if (dbus_error_is_set(error))
178         return NULL;
179
180     if (type == ALSA_TYPE_SINK) {
181         module_name = "module-alsa-sink";
182         snprintf(args, sizeof(args), "device=hw:%u sink_name=alsa_output.%s", card, strip_udi(udi));
183     } else {
184         module_name = "module-alsa-source";
185         snprintf(args, sizeof(args), "device=hw:%u source_name=alsa_input.%s", card, strip_udi(udi));
186     }
187
188     return pa_module_load(u->core, module_name, args);
189 }
190
191 #endif
192
193 #ifdef HAVE_OSS
194 static dbus_bool_t hal_device_is_oss_pcm(LibHalContext *ctx, const char *udi,
195                                          DBusError *error)
196 {
197     dbus_bool_t rv = FALSE;
198     char* type, *device_file = NULL;
199     int device;
200
201     type = libhal_device_get_property_string(ctx, udi, "oss.type", error);
202     if (!type || dbus_error_is_set(error))
203         return FALSE;
204
205     if (!strcmp(type, "pcm")) {
206         char *e;
207
208         device = libhal_device_get_property_int(ctx, udi, "oss.device", error);
209         if (dbus_error_is_set(error) || device != 0)
210             goto exit;
211
212         device_file = libhal_device_get_property_string(ctx, udi, "oss.device_file",
213                                                    error);
214         if (!device_file || dbus_error_is_set(error))
215             goto exit;
216
217         /* hack to ignore /dev/audio style devices */
218         if ((e = strrchr(device_file, '/')))
219             rv = !pa_startswith(e + 1, "audio");
220     }
221
222 exit:
223     libhal_free_string(type);
224     libhal_free_string(device_file);
225     return rv;
226 }
227
228 static pa_module* hal_device_load_oss(struct userdata *u, const char *udi,
229                                       DBusError  *error)
230 {
231     char args[256];
232     char* device;
233
234     if (!hal_device_is_oss_pcm(u->ctx, udi, error) || dbus_error_is_set(error))
235         return NULL;
236
237     device = libhal_device_get_property_string(u->ctx, udi, "oss.device_file",
238                                                error);
239     if (!device || dbus_error_is_set(error))
240         return NULL;
241
242     snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi));
243     libhal_free_string(device);
244
245     return pa_module_load(u->core, "module-oss", args);
246 }
247 #endif
248
249 static dbus_bool_t hal_device_add(struct userdata *u, const char *udi,
250                                   DBusError *error)
251 {
252     pa_module* m;
253     struct device *d;
254
255     switch(u->capability) {
256 #ifdef HAVE_ALSA
257         case CAP_ALSA:
258             m = hal_device_load_alsa(u, udi, error);
259             break;
260 #endif
261 #ifdef HAVE_OSS
262         case CAP_OSS:
263             m = hal_device_load_oss(u, udi, error);
264             break;
265 #endif
266         default:
267             assert(FALSE); /* never reached */
268             break;
269     }
270
271     if (!m || dbus_error_is_set(error))
272         return FALSE;
273
274     d = pa_xnew(struct device, 1);
275     d->udi = pa_xstrdup(udi);
276     d->index = m->index;
277
278     pa_hashmap_put(u->devices, d->udi, d);
279
280     return TRUE;
281 }
282
283 static int hal_device_add_all(struct userdata *u, capability_t capability)
284 {
285     DBusError error;
286     int i,n,count;
287     dbus_bool_t r;
288     char** udis;
289     const char* cap = get_capability_name(capability);
290
291     assert(capability < CAP_MAX);
292
293     pa_log_info("Trying capability %u (%s)", capability, cap);
294     dbus_error_init(&error);
295     udis = libhal_find_device_by_capability(u->ctx, cap, &n, &error);
296     if (dbus_error_is_set(&error)) {
297         pa_log_error("Error finding devices: %s: %s", error.name,
298                      error.message);
299         dbus_error_free(&error);
300         return -1;
301     }
302     count = 0;
303     u->capability = capability;
304     for (i = 0; i < n; ++i) {
305         r = hal_device_add(u, udis[i], &error);
306         if (dbus_error_is_set(&error)) {
307             pa_log_error("Error adding device: %s: %s", error.name,
308                          error.message);
309             dbus_error_free(&error);
310             count = -1;
311             break;
312         }
313         if (r)
314             ++count;
315     }
316
317     libhal_free_string_array(udis);
318     return count;
319 }
320
321 static dbus_bool_t device_has_capability(LibHalContext *ctx, const char *udi,
322                                          const char* cap, DBusError *error)
323 {
324     dbus_bool_t has_prop;
325     has_prop = libhal_device_property_exists(ctx, udi, "info.capabilities",
326                                              error);
327     if (!has_prop || dbus_error_is_set(error))
328         return FALSE;
329
330     return libhal_device_query_capability(ctx, udi, cap, error);
331 }
332
333 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev,
334                                  const struct timeval *tv, void *userdata)
335 {
336     DBusError error;
337     struct timerdata *td = (struct timerdata*) userdata;
338
339     dbus_error_init(&error);
340     if (libhal_device_exists(td->u->ctx, td->udi, &error))
341         hal_device_add(td->u, td->udi, &error);
342
343     if (dbus_error_is_set(&error)) {
344         pa_log_error("Error adding device: %s: %s", error.name,
345                      error.message);
346         dbus_error_free(&error);
347     }
348
349     pa_xfree(td->udi);
350     pa_xfree(td);
351     ea->time_free(ev);
352 }
353
354 static void device_added_cb(LibHalContext *ctx, const char *udi)
355 {
356     DBusError error;
357     struct timeval tv;
358     dbus_bool_t has_cap;
359     struct timerdata *t;
360     struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
361     const char* cap = get_capability_name(u->capability);
362
363     pa_log_debug("HAL Device added: %s", udi);
364
365     dbus_error_init(&error);
366     has_cap = device_has_capability(ctx, udi, cap, &error);
367     if (dbus_error_is_set(&error)) {
368         pa_log_error("Error getting capability: %s: %s", error.name,
369                      error.message);
370         dbus_error_free(&error);
371         return;
372     }
373
374     /* skip it */
375     if (!has_cap)
376         return;
377
378     /* actually add the device 1/2 second later */
379     t = pa_xnew(struct timerdata, 1);
380     t->u = u;
381     t->udi = pa_xstrdup(udi);
382
383     pa_gettimeofday(&tv);
384     pa_timeval_add(&tv, 500000);
385     u->core->mainloop->time_new(u->core->mainloop, &tv,
386                                 device_added_time_cb, t);
387 }
388
389 static void device_removed_cb(LibHalContext* ctx, const char *udi)
390 {
391     struct device *d;
392     struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
393
394     pa_log_debug("Device removed: %s", udi);
395     if ((d = pa_hashmap_remove(u->devices, udi))) {
396         pa_module_unload_by_index(u->core, d->index);
397         hal_device_free(d);
398     }
399 }
400
401 static void new_capability_cb(LibHalContext *ctx, const char *udi,
402                               const char* capability)
403 {
404     struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
405     const char* capname = get_capability_name(u->capability);
406
407     if (capname && !strcmp(capname, capability)) {
408         /* capability we care about, pretend it's a new device */
409         device_added_cb(ctx, udi);
410     }
411 }
412
413 static void lost_capability_cb(LibHalContext *ctx, const char *udi,
414                                const char* capability)
415 {
416     struct userdata *u = (struct userdata*) libhal_ctx_get_user_data(ctx);
417     const char* capname = get_capability_name(u->capability);
418
419     if (capname && !strcmp(capname, capability)) {
420         /* capability we care about, pretend it was removed */
421         device_removed_cb(ctx, udi);
422     }
423 }
424
425 #if 0
426 static void property_modified_cb(LibHalContext *ctx, const char *udi,
427                                  const char* key,
428                                  dbus_bool_t is_removed,
429                                  dbus_bool_t is_added)
430 {
431 }
432 #endif
433
434 static void pa_hal_context_free(LibHalContext* hal_ctx)
435 {
436     DBusError error;
437
438     dbus_error_init(&error);
439     libhal_ctx_shutdown(hal_ctx, &error);
440     libhal_ctx_free(hal_ctx);
441
442     if (dbus_error_is_set(&error)) {
443         dbus_error_free(&error);
444     }
445 }
446
447 static void userdata_free(struct userdata *u) {
448     pa_hal_context_free(u->ctx);
449     /* free the devices with the hashmap */
450     pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
451     pa_dbus_connection_unref(u->conn);
452     pa_xfree(u);
453 }
454
455 static LibHalContext* pa_hal_context_new(pa_core* c, DBusConnection *conn)
456 {
457     DBusError error;
458     LibHalContext *hal_ctx = NULL;
459
460     dbus_error_init(&error);
461     if (!(hal_ctx = libhal_ctx_new())) {
462         pa_log_error("libhal_ctx_new() failed");
463         goto fail;
464     }
465
466     if (!libhal_ctx_set_dbus_connection(hal_ctx, conn)) {
467         pa_log_error("Error establishing DBUS connection: %s: %s",
468                      error.name, error.message);
469         goto fail;
470     }
471
472     if (!libhal_ctx_init(hal_ctx, &error)) {
473         pa_log_error("Couldn't connect to hald: %s: %s",
474                      error.name, error.message);
475         goto fail;
476     }
477
478     return hal_ctx;
479
480 fail:
481     if (hal_ctx)
482         pa_hal_context_free(hal_ctx);
483
484     if (dbus_error_is_set(&error))
485         dbus_error_free(&error);
486
487     return NULL;
488 }
489
490 int pa__init(pa_core *c, pa_module*m) {
491     int n;
492     DBusError error;
493     pa_dbus_connection *conn;
494     struct userdata *u = NULL;
495     LibHalContext *hal_ctx = NULL;
496
497     assert(c);
498     assert(m);
499
500     dbus_error_init(&error);
501     if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) {
502         pa_log_error("Unable to contact DBUS system bus: %s: %s",
503                      error.name, error.message);
504         dbus_error_free(&error);
505         return -1;
506     }
507
508     if (!(hal_ctx = pa_hal_context_new(c, pa_dbus_connection_get(conn)))) {
509         /* pa_hal_context_new() logs appropriate errors */
510         return -1;
511     }
512
513     u = pa_xnew(struct userdata, 1);
514     u->core = c;
515     u->ctx = hal_ctx;
516     u->conn = conn;
517     u->devices = pa_hashmap_new(pa_idxset_string_hash_func,
518                                 pa_idxset_string_compare_func);
519     m->userdata = (void*) u;
520
521 #ifdef HAVE_ALSA
522     if ((n = hal_device_add_all(u, CAP_ALSA)) <= 0)
523 #endif
524 #ifdef HAVE_OSS
525     if ((n = hal_device_add_all(u, CAP_OSS)) <= 0)
526 #endif
527     {
528         pa_log_warn("failed to detect any sound hardware.");
529         userdata_free(u);
530         return -1;
531     }
532
533     libhal_ctx_set_user_data(hal_ctx, (void*) u);
534     libhal_ctx_set_device_added(hal_ctx, device_added_cb);
535     libhal_ctx_set_device_removed(hal_ctx, device_removed_cb);
536     libhal_ctx_set_device_new_capability(hal_ctx, new_capability_cb);
537     libhal_ctx_set_device_lost_capability(hal_ctx, lost_capability_cb);
538     /*libhal_ctx_set_device_property_modified(hal_ctx, property_modified_cb);*/
539
540     dbus_error_init(&error);
541     if (!libhal_device_property_watch_all(hal_ctx, &error)) {
542         pa_log_error("error monitoring device list: %s: %s",
543                      error.name, error.message);
544         dbus_error_free(&error);
545         userdata_free(u);
546         return -1;
547     }
548
549     pa_log_info("loaded %i modules.", n);
550
551     return 0;
552 }
553
554
555 void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
556     assert (c && m);
557
558     /* free the user data */
559     userdata_free(m->userdata);
560 }