]> code.delx.au - pulseaudio/blobdiff - src/modules/module-hal-detect.c
avoid duplicate loading of modules
[pulseaudio] / src / modules / module-hal-detect.c
index 84ac0ce4ddcfe15ad4157e197ceff99aa70c3e55..d73ca0ce10119be87f31c5eafa1b7a67b5af3803 100644 (file)
@@ -44,6 +44,8 @@
 #include <pulsecore/hashmap.h>
 #include <pulsecore/idxset.h>
 #include <pulsecore/core-util.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/core-scache.h>
 
 #include <hal/libhal.h>
 
@@ -57,6 +59,7 @@ PA_MODULE_VERSION(PACKAGE_VERSION)
 struct device {
     uint32_t index;
     char *udi;
+    char *sink_name, *source_name;
 };
 
 struct userdata {
@@ -79,6 +82,8 @@ static void hal_device_free(struct device* d) {
     pa_assert(d);
     
     pa_xfree(d->udi);
+    pa_xfree(d->sink_name);
+    pa_xfree(d->source_name);
     pa_xfree(d);
 }
 
@@ -136,15 +141,22 @@ static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBu
     return r;
 }
 
-static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi) {
+static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
     char args[128];
     alsa_type_t type;
     int device, card;
     const char *module_name;
     DBusError error;
+    pa_module *m;
     
     dbus_error_init(&error);
 
+    pa_assert(u);
+    pa_assert(sink_name);
+    pa_assert(source_name);
+
+    *sink_name = *source_name = NULL;
+    
     type = hal_alsa_device_get_type(u->context, udi, &error);
     if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER)
         goto fail;
@@ -161,16 +173,28 @@ static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi) {
         goto fail;
 
     if (type == ALSA_TYPE_SINK) {
+        *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
+        
         module_name = "module-alsa-sink";
-        pa_snprintf(args, sizeof(args), "device=hw:%u sink_name=alsa_output.%s", card, strip_udi(udi));
+        pa_snprintf(args, sizeof(args), "device=hw:%u sink_name=%s", card, *sink_name);
     } else {
+        *source_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
+        
         module_name = "module-alsa-source";
-        pa_snprintf(args, sizeof(args), "device=hw:%u source_name=alsa_input.%s", card, strip_udi(udi));
+        pa_snprintf(args, sizeof(args), "device=hw:%u source_name=%s", card, *source_name);
     }
 
     pa_log_debug("Loading %s with arguments '%s'", module_name, args);
 
-    return pa_module_load(u->core, module_name, args);
+    m = pa_module_load(u->core, module_name, args);
+
+    if (!m) {
+        pa_xfree(*sink_name);
+        pa_xfree(*source_name);
+        *sink_name = *source_name = NULL;
+    }
+    
+    return m;
 
 fail:
     if (dbus_error_is_set(&error)) {
@@ -219,13 +243,20 @@ finish:
     return r;
 }
 
-static pa_module* hal_device_load_oss(struct userdata *u, const char *udi) {
+static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
     char args[256];
     char* device;
     DBusError error;
+    pa_module *m;
     
     dbus_error_init(&error);
 
+    pa_assert(u);
+    pa_assert(sink_name);
+    pa_assert(source_name);
+
+    *sink_name = *source_name = NULL;
+
     if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error))
         goto fail;
 
@@ -233,12 +264,23 @@ static pa_module* hal_device_load_oss(struct userdata *u, const char *udi) {
     if (!device || dbus_error_is_set(&error))
         goto fail;
 
-    pa_snprintf(args, sizeof(args), "device=%s sink_name=oss_output.%s source_name=oss_input.%s", device, strip_udi(udi), strip_udi(udi));
+    *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
+    *source_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
+    
+    pa_snprintf(args, sizeof(args), "device=%s sink_name=%s source_name=%s", device, sink_name, source_name);
     libhal_free_string(device);
 
     pa_log_debug("Loading module-oss with arguments '%s'", args);
 
-    return pa_module_load(u->core, "module-oss", args);
+    m = pa_module_load(u->core, "module-oss", args);
+
+    if (!m) {
+        pa_xfree(*sink_name);
+        pa_xfree(*source_name);
+        *sink_name = *source_name = NULL;
+    }
+
+    return m;
 
 fail:
     if (dbus_error_is_set(&error)) {
@@ -250,31 +292,35 @@ fail:
 }
 #endif
 
-static int hal_device_add(struct userdata *u, const char *udi) {
+static struct device* hal_device_add(struct userdata *u, const char *udi) {
     pa_module* m = NULL;
     struct device *d;
+    char *sink_name = NULL, *source_name = NULL;
 
     pa_assert(u);
     pa_assert(u->capability);
-
+    pa_assert(!pa_hashmap_get(u->devices, udi));
+    
 #ifdef HAVE_ALSA
     if (strcmp(u->capability, CAPABILITY_ALSA) == 0)
-        m = hal_device_load_alsa(u, udi);
+        m = hal_device_load_alsa(u, udi, &sink_name, &source_name);
 #endif
 #ifdef HAVE_OSS
     if (strcmp(u->capability, CAPABILITY_OSS) == 0)
-        m = hal_device_load_oss(u, udi);
+        m = hal_device_load_oss(u, udi, &sink_name, &source_name);
 #endif
 
     if (!m)
-        return -1;
+        return NULL;
 
     d = pa_xnew(struct device, 1);
     d->udi = pa_xstrdup(udi);
     d->index = m->index;
+    d->sink_name = sink_name;
+    d->source_name = source_name;
     pa_hashmap_put(u->devices, d->udi, d);
 
-    return 0;
+    return d;
 }
 
 static int hal_device_add_all(struct userdata *u, const char *capability) {
@@ -300,10 +346,15 @@ static int hal_device_add_all(struct userdata *u, const char *capability) {
         u->capability = capability;
         
         for (i = 0; i < n; i++) {
-            if (hal_device_add(u, udis[i]) < 0)
+            struct device *d;
+            
+            if (!(d = hal_device_add(u, udis[i])))
                 pa_log_debug("Not loaded device %s", udis[i]);
-            else
+            else {
+                if (d->sink_name)
+                    pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0);
                 count++;
+            }
         }
     }
 
@@ -324,17 +375,27 @@ static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi
 static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) {
     DBusError error;
     struct timerdata *td = userdata;
-    int b;
 
     dbus_error_init(&error);
-    
-    b = libhal_device_exists(td->u->context, td->udi, &error);
-    
-    if (dbus_error_is_set(&error)) {
-        pa_log_error("Error adding device: %s: %s", error.name, error.message);
-        dbus_error_free(&error);
-    } else if (b)
-        hal_device_add(td->u, td->udi);
+
+    if (!pa_hashmap_get(td->u->devices, td->udi)) {
+        int b;
+        struct device *d;
+        
+        b = libhal_device_exists(td->u->context, td->udi, &error);
+        
+        if (dbus_error_is_set(&error)) {
+            pa_log_error("Error adding device: %s: %s", error.name, error.message);
+            dbus_error_free(&error);
+        } else if (b) {
+            if (!(d = hal_device_add(td->u, td->udi))) 
+                pa_log_debug("Not loaded device %s", td->udi);
+            else {
+                if (d->sink_name)
+                    pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0);
+            }
+        }
+    }
 
     pa_xfree(td->udi);
     pa_xfree(td);
@@ -349,6 +410,9 @@ static void device_added_cb(LibHalContext *context, const char *udi) {
     int good = 0;
 
     pa_assert_se(u = libhal_ctx_get_user_data(context));
+
+    if (pa_hashmap_get(u->devices, udi))
+        return;
     
     pa_log_debug("HAL Device added: %s", udi);
 
@@ -446,6 +510,116 @@ static void lost_capability_cb(LibHalContext *context, const char *udi, const ch
         device_removed_cb(context, udi);
 }
 
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
+    struct userdata*u = userdata;
+    DBusError error;
+
+    pa_assert(bus);
+    pa_assert(message);
+    pa_assert(u);
+    
+    dbus_error_init(&error);
+
+    pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
+                 dbus_message_get_interface(message),
+                 dbus_message_get_path(message),
+                 dbus_message_get_member(message));
+
+    if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
+        dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
+        uint32_t uid;
+        int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
+        
+        if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
+            pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
+            goto finish;
+        }
+
+        if (uid == getuid() || uid == geteuid()) {
+            struct device *d;
+            const char *udi;
+
+            udi = dbus_message_get_path(message);
+            
+            if ((d = pa_hashmap_get(u->devices, udi))) {
+                
+                if (d->sink_name) {
+                    pa_sink *sink;
+                    
+                    if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
+                        
+                        int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
+
+                        if (pa_sink_suspend(sink, suspend) >= 0) {
+                            if (!suspend && prev_suspended)
+                                pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+                            else if (suspend && !prev_suspended) {
+                                DBusMessage *msg;
+                                msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
+                                dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
+                                dbus_message_unref(msg);
+                            }
+                        }
+                    }
+                }
+
+                if (d->source_name) {
+                    pa_source *source;
+
+                    if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0)))
+                        pa_source_suspend(source, suspend);
+                }
+                
+            } else if (!suspend)
+                device_added_cb(u->context, udi);
+        }
+        
+    } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
+        /* We use this message to avoid a dirty race condition when we
+           get an ACLAdded message before the previously owning PA
+           sever has closed the device. We can remove this as
+           soon as HAL learns frevoke() */
+
+        const char *udi;
+        struct device *d;
+
+        pa_log_debug("Got dirty give up message, trying resume ...");
+
+        udi = dbus_message_get_path(message);
+        
+        if ((d = pa_hashmap_get(u->devices, udi))) {
+
+            if (d->sink_name) {
+                pa_sink *sink;
+                
+                if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) {
+
+                    int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED;
+                    
+                    if (pa_sink_suspend(sink, 0) >= 0)
+                        if (prev_suspended && !prev_suspended)
+                            pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+                }
+            }
+            
+            if (d->source_name) {
+                pa_source *source;
+                
+                if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0)))
+                    pa_source_suspend(source, 0);
+            }
+            
+        } else 
+            /* Yes, we don't check the UDI for validity, but hopefully HAL will */
+            device_added_cb(u->context, udi);
+    }
+
+finish:
+    dbus_error_free(&error);
+        
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 static void hal_context_free(LibHalContext* hal_context) {
     DBusError error;
 
@@ -454,8 +628,7 @@ static void hal_context_free(LibHalContext* hal_context) {
     libhal_ctx_shutdown(hal_context, &error);
     libhal_ctx_free(hal_context);
 
-    if (dbus_error_is_set(&error))
-        dbus_error_free(&error);
+    dbus_error_free(&error);
 }
 
 static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) {
@@ -485,8 +658,7 @@ fail:
     if (hal_context)
         hal_context_free(hal_context);
 
-    if (dbus_error_is_set(&error))
-        dbus_error_free(&error);
+    dbus_error_free(&error);
 
     return NULL;
 }
@@ -503,16 +675,17 @@ int pa__init(pa_core *c, pa_module*m) {
 
     dbus_error_init(&error);
     
-    if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error))) {
+    if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
+        if (conn)
+            pa_dbus_connection_unref(conn);
         pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
-        dbus_error_free(&error);
-        return -1;
+        goto fail;
     }
 
     if (!(hal_context = hal_context_new(c, pa_dbus_connection_get(conn)))) {
         /* pa_hal_context_new() logs appropriate errors */
         pa_dbus_connection_unref(conn);
-        return -1;
+        goto fail;
     }
 
     u = pa_xnew(struct userdata, 1);
@@ -527,7 +700,7 @@ int pa__init(pa_core *c, pa_module*m) {
     n = hal_device_add_all(u, CAPABILITY_ALSA);
 #endif
 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
-    if (n <= 0)
+    if (!u->capability)
 #endif        
 #ifdef HAVE_OSS
         n += hal_device_add_all(u, CAPABILITY_OSS);
@@ -539,18 +712,36 @@ int pa__init(pa_core *c, pa_module*m) {
     libhal_ctx_set_device_new_capability(hal_context, new_capability_cb);
     libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb);
 
-    dbus_error_init(&error);
-    
     if (!libhal_device_property_watch_all(hal_context, &error)) {
         pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
-        dbus_error_free(&error);
-        pa__done(c, m);
-        return -1;
+        goto fail;
+    }
+
+    if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) {
+        pa_log_error("Failed to add filter function");
+        goto fail;
+    }
+
+    dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error);
+    if (dbus_error_is_set(&error)) {
+        pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
+        goto fail;
     }
 
+    dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',interface='org.pulseaudio.Server'", &error);
+    if (dbus_error_is_set(&error)) {
+        pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message);
+        goto fail;
+    }
+    
     pa_log_info("Loaded %i modules.", n);
 
     return 0;
+
+fail:
+    dbus_error_free(&error);
+    pa__done(c, m);
+    return -1;
 }