4 This file is part of PulseAudio.
6 Copyright 2006 Lennart Poettering
7 Copyright 2006 Shams E. King
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.
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.
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
35 #include <sys/types.h>
38 #include <pulse/xmalloc.h>
39 #include <pulse/timeval.h>
41 #include <pulsecore/core-error.h>
42 #include <pulsecore/module.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/hashmap.h>
45 #include <pulsecore/idxset.h>
46 #include <pulsecore/core-util.h>
47 #include <pulsecore/namereg.h>
48 #include <pulsecore/core-scache.h>
49 #include <pulsecore/modargs.h>
51 #include <hal/libhal.h>
53 #include "dbus-util.h"
54 #include "module-hal-detect-symdef.h"
56 PA_MODULE_AUTHOR("Shahms King");
57 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
58 PA_MODULE_VERSION(PACKAGE_VERSION);
59 PA_MODULE_LOAD_ONCE(TRUE);
60 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
61 PA_MODULE_USAGE("api=<alsa or oss>");
62 #elif defined(HAVE_ALSA)
63 PA_MODULE_USAGE("api=<alsa>");
64 #elif defined(HAVE_OSS)
65 PA_MODULE_USAGE("api=<oss>");
71 char *sink_name, *source_name;
77 LibHalContext *context;
78 pa_dbus_connection *connection;
80 const char *capability;
88 #define CAPABILITY_ALSA "alsa"
89 #define CAPABILITY_OSS "oss"
91 static const char* const valid_modargs[] = {
96 static void hal_device_free(struct device* d) {
100 pa_xfree(d->sink_name);
101 pa_xfree(d->source_name);
105 static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
109 static const char *strip_udi(const char *udi) {
112 if ((slash = strrchr(udi, '/')))
127 static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) {
131 if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error)))
132 return ALSA_TYPE_OTHER;
134 if (!strcmp(type, "playback"))
136 else if (!strcmp(type, "capture"))
137 t = ALSA_TYPE_SOURCE;
141 libhal_free_string(type);
146 static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) {
150 if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error)))
153 r = strcmp(class, "modem") == 0;
159 static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
163 const char *module_name;
167 dbus_error_init(&error);
170 pa_assert(sink_name);
171 pa_assert(source_name);
173 *sink_name = *source_name = NULL;
175 type = hal_alsa_device_get_type(u->context, udi, &error);
176 if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER)
179 device = libhal_device_get_property_int(u->context, udi, "alsa.device", &error);
180 if (dbus_error_is_set(&error) || device != 0)
183 card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
184 if (dbus_error_is_set(&error))
187 if (hal_alsa_device_is_modem(u->context, udi, &error))
190 if (type == ALSA_TYPE_SINK) {
191 *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
193 module_name = "module-alsa-sink";
194 args = pa_sprintf_malloc("device_id=%u sink_name=%s", card, *sink_name);
196 *source_name = pa_sprintf_malloc("alsa_input.%s", strip_udi(udi));
198 module_name = "module-alsa-source";
199 args = pa_sprintf_malloc("device_id=%u source_name=%s", card, *source_name);
202 pa_log_debug("Loading %s with arguments '%s'", module_name, args);
204 m = pa_module_load(u->core, module_name, args);
209 pa_xfree(*sink_name);
210 pa_xfree(*source_name);
211 *sink_name = *source_name = NULL;
217 if (dbus_error_is_set(&error)) {
218 pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error.name, error.message);
219 dbus_error_free(&error);
229 static int hal_oss_device_is_pcm(LibHalContext *context, const char *udi, DBusError *error) {
230 char *class = NULL, *dev = NULL, *e;
234 class = libhal_device_get_property_string(context, udi, "oss.type", error);
235 if (dbus_error_is_set(error) || !class)
238 if (strcmp(class, "pcm"))
241 dev = libhal_device_get_property_string(context, udi, "oss.device_file", error);
242 if (dbus_error_is_set(error) || !dev)
245 if ((e = strrchr(dev, '/')))
246 if (pa_startswith(e + 1, "audio"))
249 device = libhal_device_get_property_int(context, udi, "oss.device", error);
250 if (dbus_error_is_set(error) || device != 0)
257 libhal_free_string(class);
258 libhal_free_string(dev);
263 static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
269 dbus_error_init(&error);
272 pa_assert(sink_name);
273 pa_assert(source_name);
275 *sink_name = *source_name = NULL;
277 if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error))
280 device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
281 if (!device || dbus_error_is_set(&error))
284 *sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi));
285 *source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi));
287 args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, *sink_name, *source_name);
288 libhal_free_string(device);
290 pa_log_debug("Loading module-oss with arguments '%s'", args);
291 m = pa_module_load(u->core, "module-oss", args);
295 pa_xfree(*sink_name);
296 pa_xfree(*source_name);
297 *sink_name = *source_name = NULL;
303 if (dbus_error_is_set(&error)) {
304 pa_log_error("D-Bus error while parsing OSS data: %s: %s", error.name, error.message);
305 dbus_error_free(&error);
312 static struct device* hal_device_add(struct userdata *u, const char *udi) {
315 char *sink_name = NULL, *source_name = NULL;
318 pa_assert(u->capability);
319 pa_assert(!pa_hashmap_get(u->devices, udi));
322 if (strcmp(u->capability, CAPABILITY_ALSA) == 0)
323 m = hal_device_load_alsa(u, udi, &sink_name, &source_name);
326 if (strcmp(u->capability, CAPABILITY_OSS) == 0)
327 m = hal_device_load_oss(u, udi, &sink_name, &source_name);
333 d = pa_xnew(struct device, 1);
335 d->udi = pa_xstrdup(udi);
337 d->sink_name = sink_name;
338 d->source_name = source_name;
339 pa_hashmap_put(u->devices, d->udi, d);
344 static int hal_device_add_all(struct userdata *u, const char *capability) {
351 dbus_error_init(&error);
353 if (u->capability && strcmp(u->capability, capability) != 0)
356 pa_log_info("Trying capability %s", capability);
358 udis = libhal_find_device_by_capability(u->context, capability, &n, &error);
359 if (dbus_error_is_set(&error)) {
360 pa_log_error("Error finding devices: %s: %s", error.name, error.message);
361 dbus_error_free(&error);
366 u->capability = capability;
368 for (i = 0; i < n; i++) {
371 if (!(d = hal_device_add(u, udis[i])))
372 pa_log_debug("Not loaded device %s", udis[i]);
375 pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
381 libhal_free_string_array(udis);
385 static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){
386 dbus_bool_t has_prop;
388 has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error);
389 if (!has_prop || dbus_error_is_set(error))
392 return libhal_device_query_capability(context, udi, cap, error);
395 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) {
397 struct timerdata *td = userdata;
399 dbus_error_init(&error);
401 if (!pa_hashmap_get(td->u->devices, td->udi)) {
405 b = libhal_device_exists(td->u->context, td->udi, &error);
407 if (dbus_error_is_set(&error)) {
408 pa_log_error("Error adding device: %s: %s", error.name, error.message);
409 dbus_error_free(&error);
411 if (!(d = hal_device_add(td->u, td->udi)))
412 pa_log_debug("Not loaded device %s", td->udi);
415 pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
425 static void device_added_cb(LibHalContext *context, const char *udi) {
432 pa_assert_se(u = libhal_ctx_get_user_data(context));
434 if (pa_hashmap_get(u->devices, udi))
437 pa_log_debug("HAL Device added: %s", udi);
439 dbus_error_init(&error);
443 good = device_has_capability(context, udi, u->capability, &error);
445 if (dbus_error_is_set(&error)) {
446 pa_log_error("Error getting capability: %s: %s", error.name, error.message);
447 dbus_error_free(&error);
454 good = device_has_capability(context, udi, CAPABILITY_ALSA, &error);
456 if (dbus_error_is_set(&error)) {
457 pa_log_error("Error getting capability: %s: %s", error.name, error.message);
458 dbus_error_free(&error);
463 u->capability = CAPABILITY_ALSA;
465 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
469 good = device_has_capability(context, udi, CAPABILITY_OSS, &error);
471 if (dbus_error_is_set(&error)) {
472 pa_log_error("Error getting capability: %s: %s", error.name, error.message);
473 dbus_error_free(&error);
478 u->capability = CAPABILITY_OSS;
481 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
489 /* actually add the device 1/2 second later */
490 t = pa_xnew(struct timerdata, 1);
492 t->udi = pa_xstrdup(udi);
494 pa_gettimeofday(&tv);
495 pa_timeval_add(&tv, 500000);
496 u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t);
499 static void device_removed_cb(LibHalContext* context, const char *udi) {
503 pa_assert_se(u = libhal_ctx_get_user_data(context));
505 pa_log_debug("Device removed: %s", udi);
507 if ((d = pa_hashmap_remove(u->devices, udi))) {
508 pa_module_unload_by_index(u->core, d->index);
513 static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
516 pa_assert_se(u = libhal_ctx_get_user_data(context));
518 if (!u->capability || strcmp(u->capability, capability) == 0)
519 /* capability we care about, pretend it's a new device */
520 device_added_cb(context, udi);
523 static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
526 pa_assert_se(u = libhal_ctx_get_user_data(context));
528 if (u->capability && strcmp(u->capability, capability) == 0)
529 /* capability we care about, pretend it was removed */
530 device_removed_cb(context, udi);
533 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
534 struct userdata*u = userdata;
541 dbus_error_init(&error);
543 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
544 dbus_message_get_interface(message),
545 dbus_message_get_path(message),
546 dbus_message_get_member(message));
548 if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
549 dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
551 int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
553 if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
554 pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
558 if (uid == getuid() || uid == geteuid()) {
562 udi = dbus_message_get_path(message);
564 if ((d = pa_hashmap_get(u->devices, udi))) {
565 int send_acl_race_fix_message = 0;
572 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
573 int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
575 if (prev_suspended && !suspend) {
577 if (pa_sink_suspend(sink, 0) >= 0)
578 pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
582 } else if (!prev_suspended && suspend) {
584 if (pa_sink_suspend(sink, 1) >= 0)
585 send_acl_race_fix_message = 1;
590 if (d->source_name) {
593 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) {
594 int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED;
596 if (prev_suspended && !suspend) {
598 if (pa_source_suspend(source, 0) < 0)
601 } else if (!prev_suspended && suspend) {
603 if (pa_source_suspend(source, 0) >= 0)
604 send_acl_race_fix_message = 1;
609 if (send_acl_race_fix_message) {
611 msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
612 dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
613 dbus_message_unref(msg);
617 device_added_cb(u->context, udi);
620 } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
621 /* We use this message to avoid a dirty race condition when we
622 get an ACLAdded message before the previously owning PA
623 sever has closed the device. We can remove this as
624 soon as HAL learns frevoke() */
629 udi = dbus_message_get_path(message);
631 if ((d = pa_hashmap_get(u->devices, udi)) && d->acl_race_fix) {
632 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi);
639 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
641 int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
643 if (prev_suspended) {
645 if (pa_sink_suspend(sink, 0) >= 0)
646 pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL);
651 if (d->source_name) {
654 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) {
656 int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED;
659 pa_source_suspend(source, 0);
664 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
665 device_added_cb(u->context, udi);
669 dbus_error_free(&error);
671 return DBUS_HANDLER_RESULT_HANDLED;
674 static void hal_context_free(LibHalContext* hal_context) {
677 dbus_error_init(&error);
679 libhal_ctx_shutdown(hal_context, &error);
680 libhal_ctx_free(hal_context);
682 dbus_error_free(&error);
685 static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) {
687 LibHalContext *hal_context = NULL;
689 dbus_error_init(&error);
691 if (!(hal_context = libhal_ctx_new())) {
692 pa_log_error("libhal_ctx_new() failed");
696 if (!libhal_ctx_set_dbus_connection(hal_context, conn)) {
697 pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message);
701 if (!libhal_ctx_init(hal_context, &error)) {
702 pa_log_error("Couldn't connect to hald: %s: %s", error.name, error.message);
710 hal_context_free(hal_context);
712 dbus_error_free(&error);
717 int pa__init(pa_module*m) {
719 pa_dbus_connection *conn;
720 struct userdata *u = NULL;
721 LibHalContext *hal_context = NULL;
728 dbus_error_init(&error);
730 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
731 pa_log("Failed to parse module arguments");
735 if ((api = pa_modargs_get_value(ma, "api", NULL))) {
739 if (strcmp(api, CAPABILITY_ALSA) == 0) {
741 api = CAPABILITY_ALSA;
745 if (strcmp(api, CAPABILITY_OSS) == 0) {
747 api = CAPABILITY_OSS;
752 pa_log_error("Invalid API specification.");
757 if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
759 pa_dbus_connection_unref(conn);
760 pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
764 if (!(hal_context = hal_context_new(m->core, pa_dbus_connection_get(conn)))) {
765 /* pa_hal_context_new() logs appropriate errors */
766 pa_dbus_connection_unref(conn);
770 u = pa_xnew(struct userdata, 1);
772 u->context = hal_context;
773 u->connection = conn;
774 u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
779 n = hal_device_add_all(u, CAPABILITY_ALSA);
781 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
785 n += hal_device_add_all(u, CAPABILITY_OSS);
788 libhal_ctx_set_user_data(hal_context, u);
789 libhal_ctx_set_device_added(hal_context, device_added_cb);
790 libhal_ctx_set_device_removed(hal_context, device_removed_cb);
791 libhal_ctx_set_device_new_capability(hal_context, new_capability_cb);
792 libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb);
794 if (!libhal_device_property_watch_all(hal_context, &error)) {
795 pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
799 if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) {
800 pa_log_error("Failed to add filter function");
804 dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error);
805 if (dbus_error_is_set(&error)) {
806 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
810 dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',interface='org.pulseaudio.Server'", &error);
811 if (dbus_error_is_set(&error)) {
812 pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message);
816 pa_log_info("Loaded %i modules.", n);
826 dbus_error_free(&error);
833 void pa__done(pa_module *m) {
838 if (!(u = m->userdata))
842 hal_context_free(u->context);
845 pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
848 pa_dbus_connection_unref(u->connection);