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>
50 #include <hal/libhal.h>
52 #include "dbus-util.h"
53 #include "module-hal-detect-symdef.h"
55 PA_MODULE_AUTHOR("Shahms King")
56 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
57 PA_MODULE_VERSION(PACKAGE_VERSION
)
62 char *sink_name
, *source_name
;
67 LibHalContext
*context
;
68 pa_dbus_connection
*connection
;
70 const char *capability
;
78 #define CAPABILITY_ALSA "alsa"
79 #define CAPABILITY_OSS "oss"
81 static void hal_device_free(struct device
* d
) {
85 pa_xfree(d
->sink_name
);
86 pa_xfree(d
->source_name
);
90 static void hal_device_free_cb(void *d
, PA_GCC_UNUSED
void *data
) {
94 static const char *strip_udi(const char *udi
) {
97 if ((slash
= strrchr(udi
, '/')))
112 static alsa_type_t
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
116 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", error
)))
117 return ALSA_TYPE_OTHER
;
119 if (!strcmp(type
, "playback"))
121 else if (!strcmp(type
, "capture"))
122 t
= ALSA_TYPE_SOURCE
;
126 libhal_free_string(type
);
131 static int hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
135 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", error
)))
138 r
= strcmp(class, "modem") == 0;
144 static pa_module
* hal_device_load_alsa(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
148 const char *module_name
;
152 dbus_error_init(&error
);
155 pa_assert(sink_name
);
156 pa_assert(source_name
);
158 *sink_name
= *source_name
= NULL
;
160 type
= hal_alsa_device_get_type(u
->context
, udi
, &error
);
161 if (dbus_error_is_set(&error
) || type
== ALSA_TYPE_OTHER
)
164 device
= libhal_device_get_property_int(u
->context
, udi
, "alsa.device", &error
);
165 if (dbus_error_is_set(&error
) || device
!= 0)
168 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
169 if (dbus_error_is_set(&error
))
172 if (hal_alsa_device_is_modem(u
->context
, udi
, &error
))
175 if (type
== ALSA_TYPE_SINK
) {
176 *sink_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
178 module_name
= "module-alsa-sink";
179 pa_snprintf(args
, sizeof(args
), "device=hw:%u sink_name=%s", card
, *sink_name
);
181 *source_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
183 module_name
= "module-alsa-source";
184 pa_snprintf(args
, sizeof(args
), "device=hw:%u source_name=%s", card
, *source_name
);
187 pa_log_debug("Loading %s with arguments '%s'", module_name
, args
);
189 m
= pa_module_load(u
->core
, module_name
, args
);
192 pa_xfree(*sink_name
);
193 pa_xfree(*source_name
);
194 *sink_name
= *source_name
= NULL
;
200 if (dbus_error_is_set(&error
)) {
201 pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error
.name
, error
.message
);
202 dbus_error_free(&error
);
212 static int hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
, DBusError
*error
) {
213 char *class = NULL
, *dev
= NULL
, *e
;
217 class = libhal_device_get_property_string(context
, udi
, "oss.type", error
);
218 if (dbus_error_is_set(error
) || !class)
221 if (strcmp(class, "pcm"))
224 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", error
);
225 if (dbus_error_is_set(error
) || !dev
)
228 if ((e
= strrchr(dev
, '/')))
229 if (pa_startswith(e
+ 1, "audio"))
232 device
= libhal_device_get_property_int(context
, udi
, "oss.device", error
);
233 if (dbus_error_is_set(error
) || device
!= 0)
240 libhal_free_string(class);
241 libhal_free_string(dev
);
246 static pa_module
* hal_device_load_oss(struct userdata
*u
, const char *udi
, char **sink_name
, char **source_name
) {
252 dbus_error_init(&error
);
255 pa_assert(sink_name
);
256 pa_assert(source_name
);
258 *sink_name
= *source_name
= NULL
;
260 if (!hal_oss_device_is_pcm(u
->context
, udi
, &error
) || dbus_error_is_set(&error
))
263 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
264 if (!device
|| dbus_error_is_set(&error
))
267 *sink_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
268 *source_name
= pa_sprintf_malloc("alsa_output.%s", strip_udi(udi
));
270 pa_snprintf(args
, sizeof(args
), "device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
271 libhal_free_string(device
);
273 pa_log_debug("Loading module-oss with arguments '%s'", args
);
275 m
= pa_module_load(u
->core
, "module-oss", args
);
278 pa_xfree(*sink_name
);
279 pa_xfree(*source_name
);
280 *sink_name
= *source_name
= NULL
;
286 if (dbus_error_is_set(&error
)) {
287 pa_log_error("D-Bus error while parsing OSS data: %s: %s", error
.name
, error
.message
);
288 dbus_error_free(&error
);
295 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
298 char *sink_name
= NULL
, *source_name
= NULL
;
301 pa_assert(u
->capability
);
304 if (strcmp(u
->capability
, CAPABILITY_ALSA
) == 0)
305 m
= hal_device_load_alsa(u
, udi
, &sink_name
, &source_name
);
308 if (strcmp(u
->capability
, CAPABILITY_OSS
) == 0)
309 m
= hal_device_load_oss(u
, udi
, &sink_name
, &source_name
);
315 d
= pa_xnew(struct device
, 1);
316 d
->udi
= pa_xstrdup(udi
);
318 d
->sink_name
= sink_name
;
319 d
->source_name
= source_name
;
320 pa_hashmap_put(u
->devices
, d
->udi
, d
);
325 static int hal_device_add_all(struct userdata
*u
, const char *capability
) {
331 pa_assert(!u
->capability
);
333 dbus_error_init(&error
);
335 pa_log_info("Trying capability %s", capability
);
337 udis
= libhal_find_device_by_capability(u
->context
, capability
, &n
, &error
);
338 if (dbus_error_is_set(&error
)) {
339 pa_log_error("Error finding devices: %s: %s", error
.name
, error
.message
);
340 dbus_error_free(&error
);
345 u
->capability
= capability
;
347 for (i
= 0; i
< n
; i
++) {
350 if (!(d
= hal_device_add(u
, udis
[i
])))
351 pa_log_debug("Not loaded device %s", udis
[i
]);
354 pa_scache_play_item_by_name(u
->core
, "pulse-coldplug", d
->sink_name
, PA_VOLUME_NORM
, 0);
360 libhal_free_string_array(udis
);
364 static dbus_bool_t
device_has_capability(LibHalContext
*context
, const char *udi
, const char* cap
, DBusError
*error
){
365 dbus_bool_t has_prop
;
367 has_prop
= libhal_device_property_exists(context
, udi
, "info.capabilities", error
);
368 if (!has_prop
|| dbus_error_is_set(error
))
371 return libhal_device_query_capability(context
, udi
, cap
, error
);
374 static void device_added_time_cb(pa_mainloop_api
*ea
, pa_time_event
*ev
, const struct timeval
*tv
, void *userdata
) {
376 struct timerdata
*td
= userdata
;
380 dbus_error_init(&error
);
382 b
= libhal_device_exists(td
->u
->context
, td
->udi
, &error
);
384 if (dbus_error_is_set(&error
)) {
385 pa_log_error("Error adding device: %s: %s", error
.name
, error
.message
);
386 dbus_error_free(&error
);
388 if (!(d
= hal_device_add(td
->u
, td
->udi
)))
389 pa_log_debug("Not loaded device %s", td
->udi
);
392 pa_scache_play_item_by_name(td
->u
->core
, "pulse-hotplug", d
->sink_name
, PA_VOLUME_NORM
, 0);
401 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
408 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
410 pa_log_debug("HAL Device added: %s", udi
);
412 dbus_error_init(&error
);
416 good
= device_has_capability(context
, udi
, u
->capability
, &error
);
418 if (dbus_error_is_set(&error
)) {
419 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
420 dbus_error_free(&error
);
427 good
= device_has_capability(context
, udi
, CAPABILITY_ALSA
, &error
);
429 if (dbus_error_is_set(&error
)) {
430 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
431 dbus_error_free(&error
);
436 u
->capability
= CAPABILITY_ALSA
;
438 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
442 good
= device_has_capability(context
, udi
, CAPABILITY_OSS
, &error
);
444 if (dbus_error_is_set(&error
)) {
445 pa_log_error("Error getting capability: %s: %s", error
.name
, error
.message
);
446 dbus_error_free(&error
);
451 u
->capability
= CAPABILITY_OSS
;
454 #if defined(HAVE_OSS) && defined(HAVE_ALSA)
462 /* actually add the device 1/2 second later */
463 t
= pa_xnew(struct timerdata
, 1);
465 t
->udi
= pa_xstrdup(udi
);
467 pa_gettimeofday(&tv
);
468 pa_timeval_add(&tv
, 500000);
469 u
->core
->mainloop
->time_new(u
->core
->mainloop
, &tv
, device_added_time_cb
, t
);
472 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
476 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
478 pa_log_debug("Device removed: %s", udi
);
480 if ((d
= pa_hashmap_remove(u
->devices
, udi
))) {
481 pa_module_unload_by_index(u
->core
, d
->index
);
486 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
489 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
491 if (!u
->capability
|| strcmp(u
->capability
, capability
) == 0)
492 /* capability we care about, pretend it's a new device */
493 device_added_cb(context
, udi
);
496 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
499 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
501 if (u
->capability
&& strcmp(u
->capability
, capability
) == 0)
502 /* capability we care about, pretend it was removed */
503 device_removed_cb(context
, udi
);
506 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
507 struct userdata
*u
= userdata
;
514 dbus_error_init(&error
);
516 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
517 dbus_message_get_interface(message
),
518 dbus_message_get_path(message
),
519 dbus_message_get_member(message
));
521 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
522 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
524 int suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
526 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
527 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
531 if (uid
== getuid() || uid
== geteuid()) {
535 udi
= dbus_message_get_path(message
);
537 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
542 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
544 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
546 if (pa_sink_suspend(sink
, suspend
) >= 0) {
547 if (!suspend
&& prev_suspended
)
548 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, PA_VOLUME_NORM
, 0);
549 else if (suspend
&& !prev_suspended
) {
551 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
552 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
553 dbus_message_unref(msg
);
559 if (d
->source_name
) {
562 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0)))
563 pa_source_suspend(source
, suspend
);
567 device_added_cb(u
->context
, udi
);
570 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
571 /* We use this message to avoid a dirty race condition when we
572 get an ACLAdded message before the previously owning PA
573 sever has closed the device. We can remove this as
574 soon as HAL learns frevoke() */
579 pa_log_debug("Got dirty give up message, trying resume ...");
581 udi
= dbus_message_get_path(message
);
583 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
588 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
, 0))) {
590 int prev_suspended
= pa_sink_get_state(sink
) == PA_SINK_SUSPENDED
;
592 if (pa_sink_suspend(sink
, 0) >= 0)
593 if (prev_suspended
&& !prev_suspended
)
594 pa_scache_play_item_by_name(u
->core
, "pulse-access", d
->sink_name
, PA_VOLUME_NORM
, 0);
598 if (d
->source_name
) {
601 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
, 0)))
602 pa_source_suspend(source
, 0);
606 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
607 device_added_cb(u
->context
, udi
);
611 dbus_error_free(&error
);
613 return DBUS_HANDLER_RESULT_HANDLED
;
616 static void hal_context_free(LibHalContext
* hal_context
) {
619 dbus_error_init(&error
);
621 libhal_ctx_shutdown(hal_context
, &error
);
622 libhal_ctx_free(hal_context
);
624 dbus_error_free(&error
);
627 static LibHalContext
* hal_context_new(pa_core
* c
, DBusConnection
*conn
) {
629 LibHalContext
*hal_context
= NULL
;
631 dbus_error_init(&error
);
633 if (!(hal_context
= libhal_ctx_new())) {
634 pa_log_error("libhal_ctx_new() failed");
638 if (!libhal_ctx_set_dbus_connection(hal_context
, conn
)) {
639 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
643 if (!libhal_ctx_init(hal_context
, &error
)) {
644 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
652 hal_context_free(hal_context
);
654 dbus_error_free(&error
);
659 int pa__init(pa_core
*c
, pa_module
*m
) {
661 pa_dbus_connection
*conn
;
662 struct userdata
*u
= NULL
;
663 LibHalContext
*hal_context
= NULL
;
669 dbus_error_init(&error
);
671 if (!(conn
= pa_dbus_bus_get(c
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
673 pa_dbus_connection_unref(conn
);
674 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
678 if (!(hal_context
= hal_context_new(c
, pa_dbus_connection_get(conn
)))) {
679 /* pa_hal_context_new() logs appropriate errors */
680 pa_dbus_connection_unref(conn
);
684 u
= pa_xnew(struct userdata
, 1);
686 u
->context
= hal_context
;
687 u
->connection
= conn
;
688 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
689 u
->capability
= NULL
;
693 n
= hal_device_add_all(u
, CAPABILITY_ALSA
);
695 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
699 n
+= hal_device_add_all(u
, CAPABILITY_OSS
);
702 libhal_ctx_set_user_data(hal_context
, u
);
703 libhal_ctx_set_device_added(hal_context
, device_added_cb
);
704 libhal_ctx_set_device_removed(hal_context
, device_removed_cb
);
705 libhal_ctx_set_device_new_capability(hal_context
, new_capability_cb
);
706 libhal_ctx_set_device_lost_capability(hal_context
, lost_capability_cb
);
708 if (!libhal_device_property_watch_all(hal_context
, &error
)) {
709 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
713 if (!dbus_connection_add_filter(pa_dbus_connection_get(conn
), filter_cb
, u
, NULL
)) {
714 pa_log_error("Failed to add filter function");
718 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error
);
719 if (dbus_error_is_set(&error
)) {
720 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
724 dbus_bus_add_match(pa_dbus_connection_get(conn
), "type='signal',interface='org.pulseaudio.Server'", &error
);
725 if (dbus_error_is_set(&error
)) {
726 pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error
.name
, error
.message
);
730 pa_log_info("Loaded %i modules.", n
);
735 dbus_error_free(&error
);
741 void pa__done(PA_GCC_UNUSED pa_core
*c
, pa_module
*m
) {
747 if (!(u
= m
->userdata
))
751 hal_context_free(u
->context
);
754 pa_hashmap_free(u
->devices
, hal_device_free_cb
, NULL
);
757 pa_dbus_connection_unref(u
->connection
);