diff --git a/phonon/pulsesupport.cpp b/phonon/pulsesupport.cpp index d0387c3..a3733ad 100644 --- a/phonon/pulsesupport.cpp +++ b/phonon/pulsesupport.cpp @@ -135,7 +135,6 @@ static bool s_pulseActive = false; static pa_glib_mainloop *s_mainloop = NULL; static pa_context *s_context = NULL; -static QEventLoop *s_connectionEventloop = NULL; @@ -179,31 +178,25 @@ static void createGenericDevices() } #ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER -static void ext_device_manager_subscribe_cb(pa_context *, void *); static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manager_info *info, int eol, void *userdata) { Q_ASSERT(c); Q_ASSERT(userdata); - // If this is our first iteration, set things up properly - if (s_connectionEventloop) { - logMessage("Exiting connection event loop (PulseAudio server found)"); - s_connectionEventloop->exit(0); - s_connectionEventloop = NULL; - s_pulseActive = true; - - pa_operation *o; - pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb, NULL); - if ((o = pa_ext_device_manager_subscribe(c, 1, NULL, NULL))) - pa_operation_unref(o); - } + PulseUserData *u = reinterpret_cast(userdata); if (eol < 0) { logMessage(QString("Failed to initialize device manager extension: %1").arg(pa_strerror(pa_context_errno(c)))); + logMessage("Falling back to single device mode"); createGenericDevices(); + delete u; + + // If this is our probe phase, exit now + if (s_context != c) + pa_context_disconnect(c); + return; } - PulseUserData *u = reinterpret_cast(userdata); if (eol) { // We're done reading the data, so order it by priority and copy it into the // static variables where it can then be accessed by those classes that need it. @@ -289,6 +282,8 @@ static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manage } if (s_instance) { + // This wont be emitted durring the connection probe phase + // which is intensional if (output_changed) s_instance->emitObjectDescriptionChanged(AudioOutputDeviceType); if (capture_changed) @@ -323,6 +318,11 @@ static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manage } } } + + // If this is our probe phase, exit now as we're finished reading + // our device info and can exit and reconnect + if (s_context != c) + pa_context_disconnect(c); } if (!info) @@ -374,6 +374,19 @@ static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manage } } } + +static void ext_device_manager_subscribe_cb(pa_context *c, void *) { + Q_ASSERT(c); + + pa_operation *o; + PulseUserData *u = new PulseUserData; + if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) { + logMessage(QString("pa_ext_device_manager_read() failed.")); + delete u; + return; + } + pa_operation_unref(o); +} #endif void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { @@ -515,49 +528,6 @@ static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t } -static void ext_device_manager_subscribe_cb(pa_context *c, void *) { - Q_ASSERT(c); - - pa_operation *o; -#ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER - PulseUserData *u = new PulseUserData; /** @todo Make some object to receive the info... */ - if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) { - // We need to deal with failure on first iteration - if (s_connectionEventloop) { - logMessage("Entering connection eventloop (initialisation failed)"); - s_connectionEventloop->exit(0); - s_connectionEventloop = NULL; - } - logMessage(QString("pa_ext_device_manager_read() failed")); - return; - } - pa_operation_unref(o); -#else - // If we do not have Device Manager support. We just bail out now - // and say we are active with our single "devices" for playback and capture - s_pulseActive = true; - logMessage("Entering connection eventloop (successfully detected PulseAudio)"); - if (s_connectionEventloop) { - s_connectionEventloop->exit(0); - s_connectionEventloop = NULL; - } - createGenericDevices(); -#endif - - - // Register for the stream changes... - pa_context_set_subscribe_callback(c, subscribe_cb, NULL); - - if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) - (PA_SUBSCRIPTION_MASK_SINK_INPUT| - PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) { - logMessage(QString("pa_context_subscribe() failed")); - return; - } - pa_operation_unref(o); -} - - static const char* statename(pa_context_state_t state) { switch (state) @@ -581,32 +551,68 @@ static void context_state_callback(pa_context *c, void *) Q_ASSERT(c); logMessage(QString("context_state_callback %1").arg(statename(pa_context_get_state(c)))); - switch (pa_context_get_state(c)) { - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; + pa_context_state_t state = pa_context_get_state(c); + if (state == PA_CONTEXT_READY) { + // We've connected to PA, so it is active + s_pulseActive = true; - case PA_CONTEXT_READY: - // Attempt to load things up - ext_device_manager_subscribe_cb(c, NULL); - break; + // Attempt to load things up + pa_operation *o; + + // 1. Register for the stream changes (except during probe) + if (s_context == c) { + pa_context_set_subscribe_callback(c, subscribe_cb, NULL); - case PA_CONTEXT_FAILED: - s_pulseActive = false; - if (s_connectionEventloop) { - logMessage("Entering connection eventloop (connection failed)"); - s_connectionEventloop->exit(0); - s_connectionEventloop = NULL; + if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) { + logMessage(QString("pa_context_subscribe() failed")); + return; } - break; + pa_operation_unref(o); + } - case PA_CONTEXT_TERMINATED: - default: - s_pulseActive = false; - /// @todo Deal with reconnection... - break; +#ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER + // 2a. Attempt to initialise Device Manager info (except during probe) + if (s_context == c) { + pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb, NULL); + if (!(o = pa_ext_device_manager_subscribe(c, 1, NULL, NULL))) { + logMessage(QString("pa_ext_device_manager_subscribe() failed")); + return; + } + pa_operation_unref(o); + } + + // 3. Attempt to read info from Device Manager + PulseUserData *u = new PulseUserData; + if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) { + logMessage(QString("pa_ext_device_manager_read() failed. Attempting to continue without device manager support")); + createGenericDevices(); + delete u; + + // If this is our probe phase, exit immediately + if (s_context != c) + pa_context_disconnect(c); + + return; + } + pa_operation_unref(o); + +#else + // If we know do not have Device Manager support, we just create our dummy devices now + createGenericDevices(); + + // If this is our probe phase, exit immediately + if (s_context != c) + pa_context_disconnect(c); +#endif + } else if (!PA_CONTEXT_IS_GOOD(state)) { + /// @todo Deal with reconnection... + //logMessage("Connection to PulseAudio lost"); + + // If this is our probe phase, exit our context immediately + if (s_context != c) + pa_context_disconnect(c); } } #endif // HAVE_PULSEAUDIO @@ -645,27 +651,67 @@ PulseSupport::PulseSupport() // To allow for easy debugging, give an easy way to disable this pulseaudio check QString pulseenv = qgetenv("PHONON_PULSEAUDIO_DISABLE"); - if (pulseenv.toInt()) + if (pulseenv.toInt()) { + logMessage("PulseAudio support disabled: PHONON_PULSEAUDIO_DISABLE is set"); + return; + } + + // First of all conenct to PA via simple/blocking means and if that succeeds, + // use a fully async integrated mainloop method to connect and get proper support. + pa_mainloop *p_test_mainloop; + if (!(p_test_mainloop = pa_mainloop_new())) { + logMessage("PulseAudio support disabled: Unable to create mainloop"); + return; + } + + pa_context *p_test_context; + if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "libphonon-probe"))) { + logMessage("PulseAudio support disabled: Unable to create context"); + pa_mainloop_free(p_test_mainloop); + return; + } + + logMessage("Probing for PulseAudio..."); + // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required + if (pa_context_connect(p_test_context, NULL, static_cast(0), NULL) < 0) { + logMessage(QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context)))); + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); return; + } + + pa_context_set_state_callback(p_test_context, &context_state_callback, NULL); + for (;;) { + pa_mainloop_iterate(p_test_mainloop, 1, NULL); + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { + logMessage("PulseAudio probe complete."); + break; + } + } + pa_context_disconnect(p_test_context); + pa_context_unref(p_test_context); + pa_mainloop_free(p_test_mainloop); + + if (!s_pulseActive) { + logMessage("PulseAudio support is not available."); + return; + } + + // If we're still here, PA is available. + logMessage("PulseAudio support enabled"); + + // Now we connect for real using a proper main loop that we can forget + // all about processing. s_mainloop = pa_glib_mainloop_new(NULL); Q_ASSERT(s_mainloop); pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); - // We create a simple event loop to allow the glib loop - // to iterate until we've connected or not to the server. - s_connectionEventloop = new QEventLoop; - - // XXX I don't want to show up in the client list. All I want to know is the list of sources - // and sinks... s_context = pa_context_new(api, "libphonon"); // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required - if (pa_context_connect(s_context, NULL, static_cast(0), 0) >= 0) { - pa_context_set_state_callback(s_context, &context_state_callback, s_connectionEventloop); - // Now we block until we connect or otherwise... - logMessage("Entering connection eventloop..."); - s_connectionEventloop->exec(); - } + if (pa_context_connect(s_context, NULL, static_cast(0), 0) >= 0) + pa_context_set_state_callback(s_context, &context_state_callback, NULL); #endif } @@ -681,11 +727,6 @@ PulseSupport::~PulseSupport() pa_glib_mainloop_free(s_mainloop); s_mainloop = NULL; } - - if (s_connectionEventloop) { - delete s_connectionEventloop; - s_connectionEventloop = NULL; - } #endif }