2 This file is part of PulseAudio.
4 Copyright 2006 Lennart Poettering
5 Copyright 2006 Shams E. King
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
33 #include <sys/types.h>
36 #include <pulse/xmalloc.h>
37 #include <pulse/timeval.h>
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 #include <pulsecore/namereg.h>
46 #include <pulsecore/core-scache.h>
47 #include <pulsecore/modargs.h>
49 #include <hal/libhal.h>
51 #include "dbus-util.h"
52 #include "module-hal-detect-symdef.h"
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 PA_MODULE_LOAD_ONCE(TRUE
);
58 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
59 PA_MODULE_USAGE("api=<alsa or oss>");
60 #elif defined(HAVE_ALSA)
61 PA_MODULE_USAGE("api=<alsa>");
62 #elif defined(HAVE_OSS)
63 PA_MODULE_USAGE("api=<oss>");
69 char *sink_name
, *source_name
;
75 LibHalContext
*context
;
76 pa_dbus_connection
*connection
;
78 const char *capability
;
86 #define CAPABILITY_ALSA "alsa"
87 #define CAPABILITY_OSS "oss"
89 static const char* const valid_modargs
[] = {
94 static void hal_device_free(struct device
* d
) {
98 pa_xfree(d
->sink_name
);
99 pa_xfree(d
->source_name
);
103 static void hal_device_free_cb(void *d
, PA_GCC_UNUSED
void *data
) {
107 static const char *strip_udi(const char *udi
) {
110 if ((slash
= strrchr(udi
, '/')))
125 static alsa_type_t
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
129 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", error
)))
130 return ALSA_TYPE_OTHER
;
132 if (!strcmp(type
, "playback"))
134 else if (!strcmp(type
, "capture"))
135 t
= ALSA_TYPE_SOURCE
;
139 libhal_free_string(type
);
144 static int hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
148 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", error
)))
151 r
= strcmp(class, "modem") == 0;
157 static pa_module
* hal_device_load_alsa(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
161 const char *module_name
;
165 dbus_error_init(&error
);
168 pa_assert(sink_name
);
169 pa_assert(source_name
);
171 *sink_name
= *source_name
= NULL
;
173 type
= hal_alsa_device_get_type(u
->context
, udi
, &error
);
174 if (dbus_error_is_set(&error
) || type
== ALSA_TYPE_OTHER
)
177 device
= libhal_device_get_property_int(u
->context
, udi
, "alsa.device", &error
);
178 if (dbus_error_is_set(&error
) || device
!= 0)
181 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
182 if (dbus_error_is_set(&error
))
185 if (hal_alsa_device_is_modem(u
->context
, udi
, &error
))
188 if (type
== ALSA_TYPE_SINK
) {
189 *sink_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
191 module_name
= "module-alsa-sink";
192 args
= pa_sprintf_malloc("device_id=%u sink_name=%s", card
, *sink_name
);
194 *source_name
= pa_sprintf_malloc("alsa_input.%s", strip_udi(udi
));
196 module_name
= "module-alsa-source";
197 args
= pa_sprintf_malloc("device_id=%u source_name=%s", card
, *source_name
);
200 pa_log_debug("Loading %s with arguments '%s'", module_name
, args
);
202 m
= pa_module_load(u
->core
, module_name
, args
);
207 pa_xfree(*sink_name
);
208 pa_xfree(*source_name
);
209 *sink_name
= *source_name
= NULL
;
215 if (dbus_error_is_set(&error
)) {
216 pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error
.name
, error
.message
);
217 dbus_error_free(&error
);
227 static int hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
228 char *class = NULL
, *dev
= NULL
, *e
;
232 class = libhal_device_get_property_string(context
, udi
, "oss.type", error
);
233 if (dbus_error_is_set(error
) || !class)
236 if (strcmp(class, "pcm"))
239 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", error
);
240 if (dbus_error_is_set(error
) || !dev
)
243 if ((e
= strrchr(dev
, '/')))
244 if (pa_startswith(e
+ 1, "audio"))
247 device
= libhal_device_get_property_int(context
, udi
, "oss.device", error
);
248 if (dbus_error_is_set(error
) || device
!= 0)
255 libhal_free_string(class);
256 libhal_free_string(dev
);
261 static pa_module
* hal_device_load_oss(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
267 dbus_error_init(&error
);
270 pa_assert(sink_name
);
271 pa_assert(source_name
);
273 *sink_name
= *source_name
= NULL
;
275 if (!hal_oss_device_is_pcm(u
->context
, udi
, &error
) || dbus_error_is_set(&error
))
278 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
279 if (!device
|| dbus_error_is_set(&error
))
282 *sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
283 *source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
285 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, *sink_name
, *source_name
);
286 libhal_free_string(device
);
288 pa_log_debug("Loading module-oss with arguments '%s'", args
);
289 m
= pa_module_load(u
->core
, "module-oss", args
);
293 pa_xfree(*sink_name
);
294 pa_xfree(*source_name
);
295 *sink_name
= *source_name
= NULL
;
301 if (dbus_error_is_set(&error
)) {
302 pa_log_error("D-Bus error while parsing OSS data: %s: %s", error
.name
, error
.message
);
303 dbus_error_free(&error
);
310 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
313 char *sink_name
= NULL
, *source_name
= NULL
;
316 pa_assert(u
->capability
);
317 pa_assert(!pa_hashmap_get(u
->devices
, udi
));
320 if (strcmp(u
->capability
, CAPABILITY_ALSA
) == 0)
321 m
= hal_device_load_alsa(u
, udi
, &sink_name
, &source_name
);
324 if (strcmp(u
->capability
, CAPABILITY_OSS
) == 0)
325 m
= hal_device_load_oss(u
, udi
, &sink_name
, &source_name
);
331 d
= pa_xnew(struct device
, 1);
333 d
->udi
= pa_xstrdup(udi
);
335 d
->sink_name
= sink_name
;
336 d
->source_name
= source_name
;
337 pa_hashmap_put(u
->devices
, d
->udi
, d
);
342 static int hal_device_add_all(struct userdata
*u
, const char *capability
) {
349 dbus_error_init(&error
);
351 if (u
->capability
&& strcmp(u
->capability
, capability
) != 0)
354 pa_log_info("Trying capability %s", capability
);
356 udis
= libhal_find_device_by_capability(u
->context
, capability
, &n
, &error
);
357 if (dbus_error_is_set(&error
)) {
358 pa_log_error("Error finding devices: %s: %s", error
.name
, error
.message
);
359 dbus_error_free(&error
);
364 u
->capability
= capability
;
366 for (i
= 0; i
< n
; i
++) {
369 if (!(d
= hal_device_add(u
, udis
[i
])))
370 pa_log_debug("Not loaded device %s", udis
[i
]);
373 pa_scache_play_item_by_name(u
->core
, "pulse-coldplug", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
379 libhal_free_string_array(udis
);
383 static dbus_bool_t
device_has_capability(LibHalContext
*context
, const char *udi
, const char* cap
, DBusError
*error
){
384 dbus_bool_t has_prop
;
386 has_prop
= libhal_device_property_exists(context
, udi
, "info.capabilities", error
);
387 if (!has_prop
|| dbus_error_is_set(error
))
390 return libhal_device_query_capability(context
, udi
, cap
, error
);
393 static void device_added_time_cb(pa_mainloop_api
*ea
, pa_time_event
*ev
, const struct timeval
*tv
, void *userdata
) {
395 struct timerdata
*td
= userdata
;
397 dbus_error_init(&error
);
399 if (!pa_hashmap_get(td
->u
->devices
, td
->udi
)) {
403 b
= libhal_device_exists(td
->u
->context
, td
->udi
, &error
);
405 if (dbus_error_is_set(&error
)) {
406 pa_log_error("Error adding device: %s: %s", error
.name
, error
.message
);
407 dbus_error_free(&error
);
409 if (!(d
= hal_device_add(td
->u
, td
->udi
)))
410 pa_log_debug("Not loaded device %s", td
->udi
);
413 pa_scache_play_item_by_name(td
->u
->core
, "pulse-hotplug", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
423 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
430 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
432 if (pa_hashmap_get(u
->devices
, udi
))
435 pa_log_debug("HAL Device added: %s", udi
);
437 dbus_error_init(&error
);
441 good
= device_has_capability(context
, udi
, u
->capability
, &error
);
443 if (dbus_error_is_set(&error
)) {
444 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
445 dbus_error_free(&error
);
452 good
= device_has_capability(context
, udi
, CAPABILITY_ALSA
, &error
);
454 if (dbus_error_is_set(&error
)) {
455 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
456 dbus_error_free(&error
);
461 u
->capability
= CAPABILITY_ALSA
;
463 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
467 good
= device_has_capability(context
, udi
, CAPABILITY_OSS
, &error
);
469 if (dbus_error_is_set(&error
)) {
470 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
471 dbus_error_free(&error
);
476 u
->capability
= CAPABILITY_OSS
;
479 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
487 /* actually add the device 1/2 second later */
488 t
= pa_xnew(struct timerdata
, 1);
490 t
->udi
= pa_xstrdup(udi
);
492 pa_gettimeofday(&tv
);
493 pa_timeval_add(&tv
, 500000);
494 u
->core
->mainloop
->time_new(u
->core
->mainloop
, &tv
, device_added_time_cb
, t
);
497 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
501 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
503 pa_log_debug("Device removed: %s", udi
);
505 if ((d
= pa_hashmap_remove(u
->devices
, udi
))) {
506 pa_module_unload_by_index(u
->core
, d
->index
);
511 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
514 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
516 if (!u
->capability
|| strcmp(u
->capability
, capability
) == 0)
517 /* capability we care about, pretend it's a new device */
518 device_added_cb(context
, udi
);
521 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
524 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
526 if (u
->capability
&& strcmp(u
->capability
, capability
) == 0)
527 /* capability we care about, pretend it was removed */
528 device_removed_cb(context
, udi
);
531 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
532 struct userdata
*u
= userdata
;
539 dbus_error_init(&error
);
541 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
542 dbus_message_get_interface(message
),
543 dbus_message_get_path(message
),
544 dbus_message_get_member(message
));
546 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
547 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
549 int suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
551 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
552 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
556 if (uid
== getuid() || uid
== geteuid()) {
560 udi
= dbus_message_get_path(message
);
562 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
563 int send_acl_race_fix_message
= 0;
570 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
571 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
573 if (prev_suspended
&& !suspend
) {
575 if (pa_sink_suspend(sink
, 0) >= 0)
576 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
580 } else if (!prev_suspended
&& suspend
) {
582 if (pa_sink_suspend(sink
, 1) >= 0)
583 send_acl_race_fix_message
= 1;
588 if (d
->source_name
) {
591 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0))) {
592 int prev_suspended
= pa_source_get_state(source
) == PA_SOURCE_SUSPENDED
;
594 if (prev_suspended
&& !suspend
) {
596 if (pa_source_suspend(source
, 0) < 0)
599 } else if (!prev_suspended
&& suspend
) {
601 if (pa_source_suspend(source
, 0) >= 0)
602 send_acl_race_fix_message
= 1;
607 if (send_acl_race_fix_message
) {
609 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
610 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
611 dbus_message_unref(msg
);
615 device_added_cb(u
->context
, udi
);
618 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
619 /* We use this message to avoid a dirty race condition when we
620 get an ACLAdded message before the previously owning PA
621 sever has closed the device. We can remove this as
622 soon as HAL learns frevoke() */
627 udi
= dbus_message_get_path(message
);
629 if ((d
= pa_hashmap_get(u
->devices
, udi
)) && d
->acl_race_fix
) {
630 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
637 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
639 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
641 if (prev_suspended
) {
643 if (pa_sink_suspend(sink
, 0) >= 0)
644 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
649 if (d
->source_name
) {
652 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0))) {
654 int prev_suspended
= pa_source_get_state(source
) == PA_SOURCE_SUSPENDED
;
657 pa_source_suspend(source
, 0);
662 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
663 device_added_cb(u
->context
, udi
);
667 dbus_error_free(&error
);
669 return DBUS_HANDLER_RESULT_HANDLED
;
672 static void hal_context_free(LibHalContext
* hal_context
) {
675 dbus_error_init(&error
);
677 libhal_ctx_shutdown(hal_context
, &error
);
678 libhal_ctx_free(hal_context
);
680 dbus_error_free(&error
);
683 static LibHalContext
* hal_context_new(pa_core
* c
, DBusConnection
*conn
) {
685 LibHalContext
*hal_context
= NULL
;
687 dbus_error_init(&error
);
689 if (!(hal_context
= libhal_ctx_new())) {
690 pa_log_error("libhal_ctx_new() failed");
694 if (!libhal_ctx_set_dbus_connection(hal_context
, conn
)) {
695 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
699 if (!libhal_ctx_init(hal_context
, &error
)) {
700 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
708 hal_context_free(hal_context
);
710 dbus_error_free(&error
);
715 int pa__init(pa_module
*m
) {
717 pa_dbus_connection
*conn
;
718 struct userdata
*u
= NULL
;
719 LibHalContext
*hal_context
= NULL
;
726 dbus_error_init(&error
);
728 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
729 pa_log("Failed to parse module arguments");
733 if ((api
= pa_modargs_get_value(ma
, "api", NULL
))) {
737 if (strcmp(api
, CAPABILITY_ALSA
) == 0) {
739 api
= CAPABILITY_ALSA
;
743 if (strcmp(api
, CAPABILITY_OSS
) == 0) {
745 api
= CAPABILITY_OSS
;
750 pa_log_error("Invalid API specification.");
755 if (!(conn
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
757 pa_dbus_connection_unref(conn
);
758 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
762 if (!(hal_context
= hal_context_new(m
->core
, pa_dbus_connection_get(conn
)))) {
763 /* pa_hal_context_new() logs appropriate errors */
764 pa_dbus_connection_unref(conn
);
768 u
= pa_xnew(struct userdata
, 1);
770 u
->context
= hal_context
;
771 u
->connection
= conn
;
772 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
777 n
= hal_device_add_all(u
, CAPABILITY_ALSA
);
779 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
783 n
+= hal_device_add_all(u
, CAPABILITY_OSS
);
786 libhal_ctx_set_user_data(hal_context
, u
);
787 libhal_ctx_set_device_added(hal_context
, device_added_cb
);
788 libhal_ctx_set_device_removed(hal_context
, device_removed_cb
);
789 libhal_ctx_set_device_new_capability(hal_context
, new_capability_cb
);
790 libhal_ctx_set_device_lost_capability(hal_context
, lost_capability_cb
);
792 if (!libhal_device_property_watch_all(hal_context
, &error
)) {
793 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
797 if (!dbus_connection_add_filter(pa_dbus_connection_get(conn
), filter_cb
, u
, NULL
)) {
798 pa_log_error("Failed to add filter function");
802 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error
);
803 if (dbus_error_is_set(&error
)) {
804 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
808 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',interface='org.pulseaudio.Server'", &error
);
809 if (dbus_error_is_set(&error
)) {
810 pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error
.name
, error
.message
);
814 pa_log_info("Loaded %i modules.", n
);
824 dbus_error_free(&error
);
831 void pa__done(pa_module
*m
) {
836 if (!(u
= m
->userdata
))
840 hal_context_free(u
->context
);
843 pa_hashmap_free(u
->devices
, hal_device_free_cb
, NULL
);
847 dbus_error_init(&error
);
849 dbus_bus_remove_match(pa_dbus_connection_get(u
->connection
), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error
);
850 dbus_error_free(&error
);
852 dbus_bus_remove_match(pa_dbus_connection_get(u
->connection
), "type='signal',interface='org.pulseaudio.Server'", &error
);
853 dbus_error_free(&error
);
855 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
857 pa_dbus_connection_unref(u
->connection
);