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 "tsched=<enable system timer based scheduling mode?>");
61 #elif defined(HAVE_ALSA)
62 PA_MODULE_USAGE("api=<alsa> "
63 "tsched=<enable system timer based scheduling mode?>");
64 #elif defined(HAVE_OSS)
65 PA_MODULE_USAGE("api=<oss>");
71 char *sink_name
, *source_name
;
72 pa_bool_t acl_race_fix
;
77 LibHalContext
*context
;
78 pa_dbus_connection
*connection
;
80 const char *capability
;
91 #define CAPABILITY_ALSA "alsa"
92 #define CAPABILITY_OSS "oss"
94 static const char* const valid_modargs
[] = {
102 static void hal_device_free(struct device
* d
) {
106 pa_xfree(d
->sink_name
);
107 pa_xfree(d
->source_name
);
111 static void hal_device_free_cb(void *d
, void *data
) {
115 static const char *strip_udi(const char *udi
) {
118 if ((slash
= strrchr(udi
, '/')))
133 static alsa_type_t
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
137 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", error
)))
138 return ALSA_TYPE_OTHER
;
140 if (!strcmp(type
, "playback"))
142 else if (!strcmp(type
, "capture"))
143 t
= ALSA_TYPE_SOURCE
;
147 libhal_free_string(type
);
152 static int hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
156 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", error
)))
159 r
= strcmp(class, "modem") == 0;
165 static pa_module
* hal_device_load_alsa(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
169 const char *module_name
;
173 dbus_error_init(&error
);
176 pa_assert(sink_name
);
177 pa_assert(source_name
);
179 *sink_name
= *source_name
= NULL
;
181 type
= hal_alsa_device_get_type(u
->context
, udi
, &error
);
182 if (dbus_error_is_set(&error
) || type
== ALSA_TYPE_OTHER
)
185 device
= libhal_device_get_property_int(u
->context
, udi
, "alsa.device", &error
);
186 if (dbus_error_is_set(&error
) || device
!= 0)
189 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
190 if (dbus_error_is_set(&error
))
193 if (hal_alsa_device_is_modem(u
->context
, udi
, &error
))
196 if (type
== ALSA_TYPE_SINK
) {
197 *sink_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
199 module_name
= "module-alsa-sink";
200 args
= pa_sprintf_malloc("device_id=%u sink_name=%s tsched=%i", card
, *sink_name
, (int) u
->use_tsched
);
202 *source_name
= pa_sprintf_malloc("alsa_input.%s", strip_udi(udi
));
204 module_name
= "module-alsa-source";
205 args
= pa_sprintf_malloc("device_id=%u source_name=%s tsched=%i", card
, *source_name
, (int) u
->use_tsched
);
208 pa_log_debug("Loading %s with arguments '%s'", module_name
, args
);
210 m
= pa_module_load(u
->core
, module_name
, args
);
215 pa_xfree(*sink_name
);
216 pa_xfree(*source_name
);
217 *sink_name
= *source_name
= NULL
;
223 if (dbus_error_is_set(&error
)) {
224 pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error
.name
, error
.message
);
225 dbus_error_free(&error
);
235 static int hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
236 char *class = NULL
, *dev
= NULL
, *e
;
240 class = libhal_device_get_property_string(context
, udi
, "oss.type", error
);
241 if (dbus_error_is_set(error
) || !class)
244 if (strcmp(class, "pcm"))
247 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", error
);
248 if (dbus_error_is_set(error
) || !dev
)
251 if ((e
= strrchr(dev
, '/')))
252 if (pa_startswith(e
+ 1, "audio"))
255 device
= libhal_device_get_property_int(context
, udi
, "oss.device", error
);
256 if (dbus_error_is_set(error
) || device
!= 0)
263 libhal_free_string(class);
264 libhal_free_string(dev
);
269 static pa_module
* hal_device_load_oss(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
275 dbus_error_init(&error
);
278 pa_assert(sink_name
);
279 pa_assert(source_name
);
281 *sink_name
= *source_name
= NULL
;
283 if (!hal_oss_device_is_pcm(u
->context
, udi
, &error
) || dbus_error_is_set(&error
))
286 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
287 if (!device
|| dbus_error_is_set(&error
))
290 *sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
291 *source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
293 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, *sink_name
, *source_name
);
294 libhal_free_string(device
);
296 pa_log_debug("Loading module-oss with arguments '%s'", args
);
297 m
= pa_module_load(u
->core
, "module-oss", args
);
301 pa_xfree(*sink_name
);
302 pa_xfree(*source_name
);
303 *sink_name
= *source_name
= NULL
;
309 if (dbus_error_is_set(&error
)) {
310 pa_log_error("D-Bus error while parsing OSS data: %s: %s", error
.name
, error
.message
);
311 dbus_error_free(&error
);
318 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
321 char *sink_name
= NULL
, *source_name
= NULL
;
324 pa_assert(u
->capability
);
325 pa_assert(!pa_hashmap_get(u
->devices
, udi
));
328 if (strcmp(u
->capability
, CAPABILITY_ALSA
) == 0)
329 m
= hal_device_load_alsa(u
, udi
, &sink_name
, &source_name
);
332 if (strcmp(u
->capability
, CAPABILITY_OSS
) == 0)
333 m
= hal_device_load_oss(u
, udi
, &sink_name
, &source_name
);
339 d
= pa_xnew(struct device
, 1);
340 d
->acl_race_fix
= FALSE
;
341 d
->udi
= pa_xstrdup(udi
);
343 d
->sink_name
= sink_name
;
344 d
->source_name
= source_name
;
345 pa_hashmap_put(u
->devices
, d
->udi
, d
);
350 static int hal_device_add_all(struct userdata
*u
, const char *capability
) {
357 dbus_error_init(&error
);
359 if (u
->capability
&& strcmp(u
->capability
, capability
) != 0)
362 pa_log_info("Trying capability %s", capability
);
364 udis
= libhal_find_device_by_capability(u
->context
, capability
, &n
, &error
);
365 if (dbus_error_is_set(&error
)) {
366 pa_log_error("Error finding devices: %s: %s", error
.name
, error
.message
);
367 dbus_error_free(&error
);
372 u
->capability
= capability
;
374 for (i
= 0; i
< n
; i
++) {
377 if (!(d
= hal_device_add(u
, udis
[i
])))
378 pa_log_debug("Not loaded device %s", udis
[i
]);
381 pa_scache_play_item_by_name(u
->core
, "pulse-coldplug", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
387 libhal_free_string_array(udis
);
391 static dbus_bool_t
device_has_capability(LibHalContext
*context
, const char *udi
, const char* cap
, DBusError
*error
){
392 dbus_bool_t has_prop
;
394 has_prop
= libhal_device_property_exists(context
, udi
, "info.capabilities", error
);
395 if (!has_prop
|| dbus_error_is_set(error
))
398 return libhal_device_query_capability(context
, udi
, cap
, error
);
401 static void device_added_time_cb(pa_mainloop_api
*ea
, pa_time_event
*ev
, const struct timeval
*tv
, void *userdata
) {
403 struct timerdata
*td
= userdata
;
405 dbus_error_init(&error
);
407 if (!pa_hashmap_get(td
->u
->devices
, td
->udi
)) {
411 b
= libhal_device_exists(td
->u
->context
, td
->udi
, &error
);
413 if (dbus_error_is_set(&error
)) {
414 pa_log_error("Error adding device: %s: %s", error
.name
, error
.message
);
415 dbus_error_free(&error
);
417 if (!(d
= hal_device_add(td
->u
, td
->udi
)))
418 pa_log_debug("Not loaded device %s", td
->udi
);
421 pa_scache_play_item_by_name(td
->u
->core
, "pulse-hotplug", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
431 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
436 pa_bool_t good
= FALSE
;
438 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
440 if (pa_hashmap_get(u
->devices
, udi
))
443 pa_log_debug("HAL Device added: %s", udi
);
445 dbus_error_init(&error
);
449 good
= device_has_capability(context
, udi
, u
->capability
, &error
);
451 if (dbus_error_is_set(&error
)) {
452 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
453 dbus_error_free(&error
);
460 good
= device_has_capability(context
, udi
, CAPABILITY_ALSA
, &error
);
462 if (dbus_error_is_set(&error
)) {
463 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
464 dbus_error_free(&error
);
469 u
->capability
= CAPABILITY_ALSA
;
471 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
475 good
= device_has_capability(context
, udi
, CAPABILITY_OSS
, &error
);
477 if (dbus_error_is_set(&error
)) {
478 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
479 dbus_error_free(&error
);
484 u
->capability
= CAPABILITY_OSS
;
487 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
495 /* actually add the device 1/2 second later */
496 t
= pa_xnew(struct timerdata
, 1);
498 t
->udi
= pa_xstrdup(udi
);
500 pa_gettimeofday(&tv
);
501 pa_timeval_add(&tv
, 500000);
502 u
->core
->mainloop
->time_new(u
->core
->mainloop
, &tv
, device_added_time_cb
, t
);
505 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
509 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
511 pa_log_debug("Device removed: %s", udi
);
513 if ((d
= pa_hashmap_remove(u
->devices
, udi
))) {
514 pa_module_unload_by_index(u
->core
, d
->index
, TRUE
);
519 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
522 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
524 if (!u
->capability
|| strcmp(u
->capability
, capability
) == 0)
525 /* capability we care about, pretend it's a new device */
526 device_added_cb(context
, udi
);
529 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
532 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
534 if (u
->capability
&& strcmp(u
->capability
, capability
) == 0)
535 /* capability we care about, pretend it was removed */
536 device_removed_cb(context
, udi
);
539 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
540 struct userdata
*u
= userdata
;
547 dbus_error_init(&error
);
549 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
550 dbus_message_get_interface(message
),
551 dbus_message_get_path(message
),
552 dbus_message_get_member(message
));
554 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
555 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
557 int suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
559 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
560 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
564 if (uid
== getuid() || uid
== geteuid()) {
568 udi
= dbus_message_get_path(message
);
570 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
571 pa_bool_t send_acl_race_fix_message
= FALSE
;
573 d
->acl_race_fix
= FALSE
;
578 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
579 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
581 if (prev_suspended
&& !suspend
) {
583 if (pa_sink_suspend(sink
, 0) >= 0)
584 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
586 d
->acl_race_fix
= TRUE
;
588 } else if (!prev_suspended
&& suspend
) {
590 if (pa_sink_suspend(sink
, 1) >= 0)
591 send_acl_race_fix_message
= TRUE
;
596 if (d
->source_name
) {
599 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0))) {
600 int prev_suspended
= pa_source_get_state(source
) == PA_SOURCE_SUSPENDED
;
602 if (prev_suspended
&& !suspend
) {
604 if (pa_source_suspend(source
, 0) < 0)
605 d
->acl_race_fix
= TRUE
;
607 } else if (!prev_suspended
&& suspend
) {
609 if (pa_source_suspend(source
, 0) >= 0)
610 send_acl_race_fix_message
= TRUE
;
615 if (send_acl_race_fix_message
) {
617 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
618 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
619 dbus_message_unref(msg
);
623 device_added_cb(u
->context
, udi
);
626 return DBUS_HANDLER_RESULT_HANDLED
;
628 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
629 /* We use this message to avoid a dirty race condition when we
630 get an ACLAdded message before the previously owning PA
631 sever has closed the device. We can remove this as
632 soon as HAL learns frevoke() */
637 udi
= dbus_message_get_path(message
);
639 if ((d
= pa_hashmap_get(u
->devices
, udi
)) && d
->acl_race_fix
) {
640 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
642 d
->acl_race_fix
= FALSE
;
647 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
649 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
651 if (prev_suspended
) {
653 if (pa_sink_suspend(sink
, 0) >= 0)
654 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, FALSE
, PA_VOLUME_NORM
, NULL
, NULL
);
659 if (d
->source_name
) {
662 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0))) {
664 int prev_suspended
= pa_source_get_state(source
) == PA_SOURCE_SUSPENDED
;
667 pa_source_suspend(source
, 0);
672 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
673 device_added_cb(u
->context
, udi
);
675 return DBUS_HANDLER_RESULT_HANDLED
;
679 dbus_error_free(&error
);
681 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
684 static void hal_context_free(LibHalContext
* hal_context
) {
687 dbus_error_init(&error
);
689 libhal_ctx_shutdown(hal_context
, &error
);
690 libhal_ctx_free(hal_context
);
692 dbus_error_free(&error
);
695 static LibHalContext
* hal_context_new(pa_core
* c
, DBusConnection
*conn
) {
697 LibHalContext
*hal_context
= NULL
;
699 dbus_error_init(&error
);
701 if (!(hal_context
= libhal_ctx_new())) {
702 pa_log_error("libhal_ctx_new() failed");
706 if (!libhal_ctx_set_dbus_connection(hal_context
, conn
)) {
707 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
711 if (!libhal_ctx_init(hal_context
, &error
)) {
712 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
720 hal_context_free(hal_context
);
722 dbus_error_free(&error
);
727 int pa__init(pa_module
*m
) {
729 pa_dbus_connection
*conn
;
730 struct userdata
*u
= NULL
;
731 LibHalContext
*hal_context
= NULL
;
735 pa_bool_t use_tsched
= TRUE
;
739 dbus_error_init(&error
);
741 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
742 pa_log("Failed to parse module arguments");
746 if (pa_modargs_get_value_boolean(ma
, "tsched", &use_tsched
) < 0) {
747 pa_log("Failed to parse tsched argument.");
751 if ((api
= pa_modargs_get_value(ma
, "api", NULL
))) {
752 pa_bool_t good
= FALSE
;
755 if (strcmp(api
, CAPABILITY_ALSA
) == 0) {
757 api
= CAPABILITY_ALSA
;
761 if (strcmp(api
, CAPABILITY_OSS
) == 0) {
763 api
= CAPABILITY_OSS
;
768 pa_log_error("Invalid API specification.");
773 if (!(conn
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
775 pa_dbus_connection_unref(conn
);
776 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
780 if (!(hal_context
= hal_context_new(m
->core
, pa_dbus_connection_get(conn
)))) {
781 /* pa_hal_context_new() logs appropriate errors */
782 pa_dbus_connection_unref(conn
);
786 u
= pa_xnew(struct userdata
, 1);
788 u
->context
= hal_context
;
789 u
->connection
= conn
;
790 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
793 u
->use_tsched
= use_tsched
;
798 n
= hal_device_add_all(u
, CAPABILITY_ALSA
);
800 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
804 n
+= hal_device_add_all(u
, CAPABILITY_OSS
);
807 libhal_ctx_set_user_data(hal_context
, u
);
808 libhal_ctx_set_device_added(hal_context
, device_added_cb
);
809 libhal_ctx_set_device_removed(hal_context
, device_removed_cb
);
810 libhal_ctx_set_device_new_capability(hal_context
, new_capability_cb
);
811 libhal_ctx_set_device_lost_capability(hal_context
, lost_capability_cb
);
813 if (!libhal_device_property_watch_all(hal_context
, &error
)) {
814 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
818 if (!dbus_connection_add_filter(pa_dbus_connection_get(conn
), filter_cb
, u
, NULL
)) {
819 pa_log_error("Failed to add filter function");
823 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error
);
824 if (dbus_error_is_set(&error
)) {
825 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
829 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',interface='org.pulseaudio.Server'", &error
);
830 if (dbus_error_is_set(&error
)) {
831 pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error
.name
, error
.message
);
835 pa_log_info("Loaded %i modules.", n
);
845 dbus_error_free(&error
);
852 void pa__done(pa_module
*m
) {
857 if (!(u
= m
->userdata
))
861 hal_context_free(u
->context
);
864 pa_hashmap_free(u
->devices
, hal_device_free_cb
, NULL
);
868 dbus_error_init(&error
);
870 dbus_bus_remove_match(pa_dbus_connection_get(u
->connection
), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error
);
871 dbus_error_free(&error
);
873 dbus_bus_remove_match(pa_dbus_connection_get(u
->connection
), "type='signal',interface='org.pulseaudio.Server'", &error
);
874 dbus_error_free(&error
);
876 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
878 pa_dbus_connection_unref(u
->connection
);